<form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8" class="cuteform">
<ol>
- <div id="id___all__"></div>
+ <div id="id_{% if form_prefix %}{{ form_prefix }}-{% endif %}__all__"></div>
{{ form.as_ul }}
<li><input type="submit" value="{{ submit }}"/></li>
</ol>
-</form>
\ No newline at end of file
+</form>
+
+{% block extra %}{% endblock %}
\ No newline at end of file
-from django.http import HttpResponse, HttpResponseRedirect
+from functools import wraps
+
+from django.http import (HttpResponse, HttpResponseRedirect,
+ HttpResponseForbidden)
from django.shortcuts import render_to_response
from django.template import RequestContext
+from django.utils.cache import patch_vary_headers
from django.utils.encoding import force_unicode
from django.utils.functional import Promise
from django.utils.http import urlquote_plus
from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.vary import vary_on_headers
class LazyEncoder(simplejson.JSONEncoder):
super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
+def method_decorator(function_decorator):
+ """Converts a function decorator to a method decorator.
+
+ It just makes it ignore first argument.
+ """
+ def decorator(method):
+ @wraps(method)
+ def wrapped_method(self, *args, **kwargs):
+ def function(*fargs, **fkwargs):
+ return method(self, *fargs, **fkwargs)
+ return function_decorator(function)(*args, **kwargs)
+ return wrapped_method
+ return decorator
+
+
+def require_login(request):
+ """Return 403 if request is AJAX. Redirect to login page if not."""
+ if request.is_ajax():
+ return HttpResponseForbidden('Not logged in')
+ else:
+ return HttpResponseRedirect('/uzytkownicy/zaloguj')# next?=request.build_full_path())
+
class AjaxableFormView(object):
"""Subclass this to create an ajaxable view for any form.
title = ''
success_message = ''
+ POST_login = False
formname = "form"
+ form_prefix = None
full_template = "ajaxable/form_on_page.html"
- def __call__(self, request):
- """A view displaying a form, or JSON if `ajax' GET param is set."""
- ajax = request.GET.get('ajax', False)
+ @method_decorator(vary_on_headers('X-Requested-With'))
+ def __call__(self, request, *args, **kwargs):
+ """A view displaying a form, or JSON if request is AJAX."""
+ form_args, form_kwargs = self.form_args(request, *args, **kwargs)
+ if self.form_prefix:
+ form_kwargs['prefix'] = self.form_prefix
+
if request.method == "POST":
- form = self.form_class(data=request.POST)
+ # do I need to be logged in?
+ if self.POST_login and not request.user.is_authenticated():
+ return require_login(request)
+
+ form_kwargs['data'] = request.POST
+ form = self.form_class(*form_args, **form_kwargs)
if form.is_valid():
- self.success(form, request)
+ add_args = self.success(form, request)
redirect = request.GET.get('next')
- if not ajax and redirect:
+ if not request.is_ajax() and redirect:
return HttpResponseRedirect(urlquote_plus(
redirect, safe='/?=&'))
response_data = {'success': True,
'message': self.success_message, 'redirect': redirect}
- else:
- response_data = {'success': False, 'errors': form.errors}
- if ajax:
+ if add_args:
+ response_data.update(add_args)
+ elif request.is_ajax():
+ # Form was sent with errors. Send them back.
+ if self.form_prefix:
+ errors = {}
+ for key, value in form.errors.items():
+ errors["%s-%s" % (self.form_prefix, key)] = value
+ else:
+ errors = form.errors
+ response_data = {'success': False, 'errors': errors}
+ if request.is_ajax():
return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
else:
- form = self.form_class()
+ if (self.POST_login and not request.user.is_authenticated()
+ and not request.is_ajax()):
+ return require_login(request)
+
+ form = self.form_class(*form_args, **form_kwargs)
response_data = None
- template = self.template if ajax else self.full_template
- return render_to_response(template, {
+ template = self.template if request.is_ajax() else self.full_template
+ context = {
self.formname: form,
"title": self.title,
"submit": self.submit,
"response_data": response_data,
"ajax_template": self.template,
- },
+ "view_args": args,
+ "view_kwargs": kwargs,
+ }
+ context.update(self.extra_context())
+ return render_to_response(template, context,
context_instance=RequestContext(request))
+ def form_args(self, request, *args, **kwargs):
+ """Override to parse view args and give additional args to the form."""
+ return (), {}
+
+ def extra_context(self):
+ """Override to pass something to template."""
+ return {}
+
def success(self, form, request):
"""What to do when the form is valid.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
import datetime
-from functools import wraps
from django.conf import settings
from django.db import models
from django.db.models.fields.files import FieldFile
-from django.db.models import signals
from django import forms
-from django.forms.widgets import flatatt
-from django.utils.encoding import smart_unicode
from django.utils import simplejson as json
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
setattr(cls, 'set_%s_value' % self.name, set_value)
-class JQueryAutoCompleteWidget(forms.TextInput):
- def __init__(self, options, *args, **kwargs):
- self.options = dumps(options)
- super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs)
-
- def render_js(self, field_id, options):
- return u'$(\'#%s\').autocomplete(%s).result(autocomplete_result_handler);' % (field_id, options)
-
- def render(self, name, value=None, attrs=None):
- final_attrs = self.build_attrs(attrs, name=name)
- if value:
- final_attrs['value'] = smart_unicode(value)
-
- if not self.attrs.has_key('id'):
- final_attrs['id'] = 'id_%s' % name
-
- html = u'''<input type="text" %(attrs)s/>
- <script type="text/javascript">//<!--
- %(js)s//--></script>
- ''' % {
- 'attrs': flatatt(final_attrs),
- 'js' : self.render_js(final_attrs['id'], self.options),
- }
-
- return mark_safe(html)
-
-
-class JQueryAutoCompleteSearchWidget(JQueryAutoCompleteWidget):
- def __init__(self, *args, **kwargs):
- super(JQueryAutoCompleteSearchWidget, self).__init__(*args, **kwargs)
-
- def render_js(self, field_id, options):
- return u""
-
-
-class JQueryAutoCompleteField(forms.CharField):
- def __init__(self, source, options={}, *args, **kwargs):
- if 'widget' not in kwargs:
- options['source'] = source
- kwargs['widget'] = JQueryAutoCompleteWidget(options)
-
- super(JQueryAutoCompleteField, self).__init__(*args, **kwargs)
-
-
-class JQueryAutoCompleteSearchField(forms.CharField):
- def __init__(self, options={}, *args, **kwargs):
- if 'widget' not in kwargs:
- kwargs['widget'] = JQueryAutoCompleteSearchWidget(options)
-
- super(JQueryAutoCompleteSearchField, self).__init__(*args, **kwargs)
-
-
class OverwritingFieldFile(FieldFile):
"""
Deletes the old file before saving the new one.
#
from django import forms
from django.utils.translation import ugettext_lazy as _
-from slughifi import slughifi
-from catalogue.models import Tag, Book
-from catalogue.fields import JQueryAutoCompleteSearchField
-from catalogue import utils
+from catalogue.models import Book
class BookImportForm(forms.Form):
return Book.from_xml_file(self.cleaned_data['book_xml_file'], overwrite=True, **kwargs)
-class SearchForm(forms.Form):
- q = JQueryAutoCompleteSearchField() # {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"})
-
- def __init__(self, source, *args, **kwargs):
- kwargs['auto_id'] = False
- super(SearchForm, self).__init__(*args, **kwargs)
- self.fields['q'].widget.attrs['id'] = _('search')
- self.fields['q'].widget.attrs['autocomplete'] = _('off')
- self.fields['q'].widget.attrs['data-source'] = _(source)
- if not 'q' in self.data:
- self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre, phrase')
-
-
-class UserSetsForm(forms.Form):
- def __init__(self, book, user, *args, **kwargs):
- super(UserSetsForm, self).__init__(*args, **kwargs)
- self.fields['set_ids'] = forms.ChoiceField(
- choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
- )
-
-
-class ObjectSetsForm(forms.Form):
- def __init__(self, obj, user, *args, **kwargs):
- super(ObjectSetsForm, self).__init__(*args, **kwargs)
- self.fields['set_ids'] = forms.MultipleChoiceField(
- label=_('Shelves'),
- required=False,
- choices=[(tag.id, "%s (%s)" % (tag.name, tag.book_count)) for tag in Tag.objects.filter(category='set', user=user)],
- initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
- widget=forms.CheckboxSelectMultiple
- )
-
-
-class NewSetForm(forms.Form):
- name = forms.CharField(max_length=50, required=True)
-
- def __init__(self, *args, **kwargs):
- super(NewSetForm, self).__init__(*args, **kwargs)
- self.fields['name'].widget.attrs['title'] = _('Name of the new shelf')
-
- def save(self, user, commit=True):
- name = self.cleaned_data['name']
- new_set = Tag(name=name, slug=utils.get_random_hash(name), sort_key=name.lower(),
- category='set', user=user)
-
- new_set.save()
- return new_set
-
-
FORMATS = [(f, f.upper()) for f in Book.ebook_formats]
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Book._related_info'
+ db.add_column('catalogue_book', '_related_info', self.gf('jsonfield.fields.JSONField')(null=True, blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Book._related_info'
+ db.delete_column('catalogue_book', '_related_info')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'catalogue.book': {
+ 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'},
+ '_related_info': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
+ 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+ 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+ 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+ 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+ 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+ 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ 'catalogue.bookmedia': {
+ 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+ 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+ 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+ 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+ 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ 'catalogue.collection': {
+ 'Meta': {'ordering': "('title',)", 'object_name': 'Collection'},
+ 'book_slugs': ('django.db.models.fields.TextField', [], {}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'primary_key': 'True', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'})
+ },
+ 'catalogue.fragment': {
+ 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+ 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'short_text': ('django.db.models.fields.TextField', [], {}),
+ 'text': ('django.db.models.fields.TextField', [], {})
+ },
+ 'catalogue.tag': {
+ 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+ 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+ 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+ },
+ 'catalogue.tagrelation': {
+ 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['catalogue']
from django.db import models
from django.db.models import permalink, Q
import django.dispatch
-from django.core.cache import cache
+from django.core.cache import get_cache
from django.core.files.storage import DefaultStorage
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.utils.translation import get_language
from django.core.urlresolvers import reverse
from django.db.models.signals import post_save, m2m_changed, pre_delete
+import jsonfield
from django.conf import settings
import re
from os import path
-
import search
# Those are hard-coded here so that makemessages sees them.
('book', _('book')),
)
-# not quite, but Django wants you to set a timeout
-CACHE_FOREVER = 2419200 # 28 days
+
+permanent_cache = get_cache('permanent')
class TagSubcategoryManager(models.Manager):
formats = ebook_formats + ['html', 'xml']
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
+
+ _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)
+
objects = models.Manager()
tagged = managers.ModelTaggedItemManager(Tag)
tags = managers.TagDescriptor(Tag)
if self.id is None:
return
- cache_key = "Book.short_html/%d/%s"
- for lang, langname in settings.LANGUAGES:
- cache.delete(cache_key % (self.id, lang))
- cache.delete("Book.mini_box/%d" % (self.id, ))
+ type(self).objects.filter(pk=self.pk).update(_related_info=None)
# Fragment.short_html relies on book's tags, so reset it here too
for fragm in self.fragments.all():
fragm.reset_short_html()
- def short_html(self):
- if self.id:
- cache_key = "Book.short_html/%d/%s" % (self.id, get_language())
- short_html = cache.get(cache_key)
- else:
- short_html = None
-
- if short_html is not None:
- return mark_safe(short_html)
- else:
- tags = self.tags.filter(category__in=('author', 'kind', 'genre', 'epoch'))
- tags = split_tags(tags)
-
- formats = {}
- # files generated during publication
- for ebook_format in self.ebook_formats:
- if self.has_media(ebook_format):
- formats[ebook_format] = self.get_media(ebook_format)
-
-
- short_html = unicode(render_to_string('catalogue/book_short.html',
- {'book': self, 'tags': tags, 'formats': formats}))
-
- if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
- return mark_safe(short_html)
-
- def mini_box(self):
- if self.id:
- cache_key = "Book.mini_box/%d" % (self.id, )
- short_html = cache.get(cache_key)
- else:
- short_html = None
-
- if short_html is None:
- authors = self.tags.filter(category='author')
-
- short_html = unicode(render_to_string('catalogue/book_mini_box.html',
- {'book': self, 'authors': authors, 'STATIC_URL': settings.STATIC_URL}))
-
- if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
- return mark_safe(short_html)
-
def has_description(self):
return len(self.description) > 0
has_description.short_description = _('description')
cls.published.send(sender=book)
return book
+ def related_info(self):
+ """Keeps info about related objects (tags, media) in cache field."""
+ if self._related_info is not None:
+ return self._related_info
+ else:
+ rel = {'tags': {}, 'media': {}}
+ tags = self.tags.filter(category__in=(
+ 'author', 'kind', 'genre', 'epoch'))
+ tags = split_tags(tags)
+ for category in tags:
+ rel['tags'][category] = [
+ (t.name, t.get_absolute_url()) for t in tags[category]]
+ for media_format in BookMedia.formats:
+ rel['media'][media_format] = self.has_media(media_format)
+ if self.pk:
+ type(self).objects.filter(pk=self.pk).update(_related_info=rel)
+ return rel
+
def reset_tag_counter(self):
if self.id is None:
return
cache_key = "Book.tag_counter/%d" % self.id
- cache.delete(cache_key)
+ permanent_cache.delete(cache_key)
if self.parent:
self.parent.reset_tag_counter()
def tag_counter(self):
if self.id:
cache_key = "Book.tag_counter/%d" % self.id
- tags = cache.get(cache_key)
+ tags = permanent_cache.get(cache_key)
else:
tags = None
tags[tag.pk] = 1
if self.id:
- cache.set(cache_key, tags, CACHE_FOREVER)
+ permanent_cache.set(cache_key, tags)
return tags
def reset_theme_counter(self):
return
cache_key = "Book.theme_counter/%d" % self.id
- cache.delete(cache_key)
+ permanent_cache.delete(cache_key)
if self.parent:
self.parent.reset_theme_counter()
def theme_counter(self):
if self.id:
cache_key = "Book.theme_counter/%d" % self.id
- tags = cache.get(cache_key)
+ tags = permanent_cache.get(cache_key)
else:
tags = None
tags[tag.pk] = tags.get(tag.pk, 0) + 1
if self.id:
- cache.set(cache_key, tags, CACHE_FOREVER)
+ permanent_cache.set(cache_key, tags)
return tags
def pretty_title(self, html_links=False):
cache_key = "Fragment.short_html/%d/%s"
for lang, langname in settings.LANGUAGES:
- cache.delete(cache_key % (self.id, lang))
+ permanent_cache.delete(cache_key % (self.id, lang))
def short_html(self):
if self.id:
cache_key = "Fragment.short_html/%d/%s" % (self.id, get_language())
- short_html = cache.get(cache_key)
+ short_html = permanent_cache.get(cache_key)
else:
short_html = None
short_html = unicode(render_to_string('catalogue/fragment_short.html',
{'fragment': self}))
if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
+ permanent_cache.set(cache_key, short_html)
return mark_safe(short_html)
from django import template
from django.template import Node, Variable
from django.utils.encoding import smart_str
+from django.core.cache import get_cache
from django.core.urlresolvers import reverse
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.db.models import Q
from django.conf import settings
from django.utils.translation import ugettext as _
-from catalogue.forms import SearchForm
from catalogue.utils import split_tags
-
register = template.Library()
return locals()
-@register.inclusion_tag('catalogue/book_wide.html')
-def book_wide(book):
- tags = book.tags.filter(category__in=('author', 'kind', 'genre', 'epoch'))
- tags = split_tags(tags)
-
+@register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
+def book_wide(context, book):
formats = {}
# files generated during publication
for ebook_format in book.ebook_formats:
if book.has_media(ebook_format):
formats[ebook_format] = book.get_media(ebook_format)
- extra_info = book.get_extra_info_value()
-
- has_media = {}
- for media_format in ['mp3', 'ogg']:
- has_media[media_format] = book.has_media(media_format)
-
- return locals()
+ return {
+ 'related': book.related_info(),
+ 'book': book,
+ 'formats': formats,
+ 'extra_info': book.get_extra_info_value(),
+ 'request': context.get('request'),
+ }
+
+
+@register.inclusion_tag('catalogue/book_short.html', takes_context=True)
+def book_short(context, book):
+ return {
+ 'related': book.related_info(),
+ 'book': book,
+ 'request': context.get('request'),
+ }
+
+
+@register.inclusion_tag('catalogue/book_mini_box.html')
+def book_mini(book):
+ return {
+ 'related': book.related_info(),
+ 'book': book,
+ }
+
+
+@register.inclusion_tag('catalogue/work-list.html', takes_context=True)
+def work_list(context, object_list):
+ request = context.get('request')
+ if object_list:
+ object_type = type(object_list[0]).__name__
+ return locals()
\ No newline at end of file
) + \
patterns('catalogue.views',
url(r'^$', 'catalogue', name='catalogue'),
- url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'),
- url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
- url(r'^polki/$', 'user_shelves', name='user_shelves'),
- url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
- url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'),
+
url(r'^lektury/$', 'book_list', name='book_list'),
url(r'^lektury/(?P<slug>[a-zA-Z0-9-]+)/$', 'collection', name='collection'),
url(r'^audiobooki/$', 'audiobook_list', name='audiobook_list'),
url(r'^daisy/$', 'daisy_list', name='daisy_list'),
- url(r'^lektura/(?P<book>%s)/polki/' % SLUG, 'book_sets', name='book_shelves'),
- url(r'^polki/nowa/$', 'new_set', name='new_set'),
url(r'^tags/$', 'tags_starting_with', name='hint'),
url(r'^jtags/$', 'json_tags_starting_with', name='jhint'),
url(r'^szukaj/$', 'search', name='old_search'),
result = {"matches": tags_list}
return JSONResponse(result, callback)
-# ====================
-# = Shelf management =
-# ====================
-@login_required
-@cache.never_cache
-def user_shelves(request):
- shelves = models.Tag.objects.filter(category='set', user=request.user)
- new_set_form = forms.NewSetForm()
- return render_to_response('catalogue/user_shelves.html', locals(),
- context_instance=RequestContext(request))
-
-@cache.never_cache
-def book_sets(request, slug):
- if not request.user.is_authenticated():
- return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
-
- book = get_object_or_404(models.Book, slug=slug)
-
- user_sets = models.Tag.objects.filter(category='set', user=request.user)
- book_sets = book.tags.filter(category='set', user=request.user)
-
- if request.method == 'POST':
- form = forms.ObjectSetsForm(book, request.user, request.POST)
- if form.is_valid():
- old_shelves = list(book.tags.filter(category='set'))
- new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
-
- for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
- touch_tag(shelf)
-
- for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
- touch_tag(shelf)
-
- book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
- if request.is_ajax():
- return JSONResponse('{"msg":"'+_("<p>Shelves were sucessfully saved.</p>")+'", "after":"close"}')
- else:
- return HttpResponseRedirect('/')
- else:
- form = forms.ObjectSetsForm(book, request.user)
- new_set_form = forms.NewSetForm()
-
- return render_to_response('catalogue/book_sets.html', locals(),
- context_instance=RequestContext(request))
-
-
-@login_required
-@require_POST
-@cache.never_cache
-def remove_from_shelf(request, shelf, slug):
- book = get_object_or_404(models.Book, slug=slug)
-
- shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
-
- if shelf in book.tags:
- models.Tag.objects.remove_tag(book, shelf)
- touch_tag(shelf)
-
- return HttpResponse(_('Book was successfully removed from the shelf'))
- else:
- return HttpResponse(_('This book is not on the shelf'))
-
-
-def collect_books(books):
- """
- Returns all real books in collection.
- """
- result = []
- for book in books:
- if len(book.children.all()) == 0:
- result.append(book)
- else:
- result += collect_books(book.children.all())
- return result
-
-
-@cache.never_cache
-def download_shelf(request, slug):
- """"
- Create a ZIP archive on disk and transmit it in chunks of 8KB,
- without loading the whole file into memory. A similar approach can
- be used for large dynamic PDF files.
- """
- from slughifi import slughifi
- import tempfile
- import zipfile
-
- shelf = get_object_or_404(models.Tag, slug=slug, category='set')
-
- formats = []
- form = forms.DownloadFormatsForm(request.GET)
- if form.is_valid():
- formats = form.cleaned_data['formats']
- if len(formats) == 0:
- formats = models.Book.ebook_formats
-
- # Create a ZIP archive
- temp = tempfile.TemporaryFile()
- archive = zipfile.ZipFile(temp, 'w')
-
- for book in collect_books(models.Book.tagged.with_all(shelf)):
- for ebook_format in models.Book.ebook_formats:
- if ebook_format in formats and book.has_media(ebook_format):
- filename = book.get_media(ebook_format).path
- archive.write(filename, str('%s.%s' % (book.slug, ebook_format)))
- archive.close()
-
- response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
- response['Content-Disposition'] = 'attachment; filename=%s.zip' % slughifi(shelf.name)
- response['Content-Length'] = temp.tell()
-
- temp.seek(0)
- response.write(temp.read())
- return response
-
-
-@cache.never_cache
-def shelf_book_formats(request, shelf):
- """"
- Returns a list of formats of books in shelf.
- """
- shelf = get_object_or_404(models.Tag, slug=shelf, category='set')
-
- formats = {}
- for ebook_format in models.Book.ebook_formats:
- formats[ebook_format] = False
-
- for book in collect_books(models.Book.tagged.with_all(shelf)):
- for ebook_format in models.Book.ebook_formats:
- if book.has_media(ebook_format):
- formats[ebook_format] = True
-
- return HttpResponse(LazyEncoder().encode(formats))
-
-
-@login_required
-@require_POST
-@cache.never_cache
-def new_set(request):
- new_set_form = forms.NewSetForm(request.POST)
- if new_set_form.is_valid():
- new_set = new_set_form.save(request.user)
-
- if request.is_ajax():
- return JSONResponse('{"id":"%d", "name":"%s", "msg":"<p>Shelf <strong>%s</strong> was successfully created</p>"}' % (new_set.id, new_set.name, new_set))
- else:
- return HttpResponseRedirect('/')
-
- return HttpResponseRedirect('/')
-
-
-@login_required
-@require_POST
-@cache.never_cache
-def delete_shelf(request, slug):
- user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
- user_set.delete()
-
- if request.is_ajax():
- return HttpResponse(_('<p>Shelf <strong>%s</strong> was successfully removed</p>') % user_set.name)
- else:
- return HttpResponseRedirect('/')
-
# =========
# = Admin =
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from django.views.generic.list_detail import object_list
-from catalogue.forms import SearchForm
from dictionary.models import Note
def letter_notes(request, letter=None):
-from catalogue.forms import SearchForm
from django.core.urlresolvers import reverse
+from search.forms import SearchForm
def search_form(request):
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import forms
+from django.forms.widgets import flatatt
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import mark_safe
+from catalogue.fields import dumps
+
+
+class JQueryAutoCompleteWidget(forms.TextInput):
+ def __init__(self, options, *args, **kwargs):
+ self.options = dumps(options)
+ super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs)
+
+ def render_js(self, field_id, options):
+ return u'$(\'#%s\').autocomplete(%s).result(autocomplete_result_handler);' % (field_id, options)
+
+ def render(self, name, value=None, attrs=None):
+ final_attrs = self.build_attrs(attrs, name=name)
+ if value:
+ final_attrs['value'] = smart_unicode(value)
+
+ if not self.attrs.has_key('id'):
+ final_attrs['id'] = 'id_%s' % name
+
+ html = u'''<input type="text" %(attrs)s/>
+ <script type="text/javascript">//<!--
+ %(js)s//--></script>
+ ''' % {
+ 'attrs': flatatt(final_attrs),
+ 'js' : self.render_js(final_attrs['id'], self.options),
+ }
+
+ return mark_safe(html)
+
+
+class JQueryAutoCompleteSearchWidget(JQueryAutoCompleteWidget):
+ def __init__(self, *args, **kwargs):
+ super(JQueryAutoCompleteSearchWidget, self).__init__(*args, **kwargs)
+
+ def render_js(self, field_id, options):
+ return u""
+
+
+class JQueryAutoCompleteField(forms.CharField):
+ def __init__(self, source, options={}, *args, **kwargs):
+ if 'widget' not in kwargs:
+ options['source'] = source
+ kwargs['widget'] = JQueryAutoCompleteWidget(options)
+
+ super(JQueryAutoCompleteField, self).__init__(*args, **kwargs)
+
+
+class JQueryAutoCompleteSearchField(forms.CharField):
+ def __init__(self, options={}, *args, **kwargs):
+ if 'widget' not in kwargs:
+ kwargs['widget'] = JQueryAutoCompleteSearchWidget(options)
+
+ super(JQueryAutoCompleteSearchField, self).__init__(*args, **kwargs)
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from search.fields import JQueryAutoCompleteSearchField
+
+
+class SearchForm(forms.Form):
+ q = JQueryAutoCompleteSearchField() # {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"})
+
+ def __init__(self, source, *args, **kwargs):
+ kwargs['auto_id'] = False
+ super(SearchForm, self).__init__(*args, **kwargs)
+ self.fields['q'].widget.attrs['id'] = _('search')
+ self.fields['q'].widget.attrs['autocomplete'] = _('off')
+ self.fields['q'].widget.attrs['data-source'] = _(source)
+ if not 'q' in self.data:
+ self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre, phrase')
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from catalogue.models import Tag
+from catalogue import utils
+from social.utils import get_set, set_sets
+
+
+class UserSetsForm(forms.Form):
+ def __init__(self, book, user, *args, **kwargs):
+ super(UserSetsForm, self).__init__(*args, **kwargs)
+ self.fields['set_ids'] = forms.ChoiceField(
+ choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
+ )
+
+
+class ObjectSetsForm(forms.Form):
+ tags = forms.CharField(label=_('Tags (comma-separated)'), required=False)
+
+ def __init__(self, obj, user, *args, **kwargs):
+ self._obj = obj
+ self._user = user
+ data = kwargs.setdefault('data', {})
+ if 'tags' not in data and user.is_authenticated():
+ data['tags'] = ', '.join(t.name
+ for t in obj.tags.filter(category='set', user=user) if t.name)
+ super(ObjectSetsForm, self).__init__(*args, **kwargs)
+
+ def save(self, request):
+ tags = [get_set(self._user, tag_name.strip())
+ for tag_name in self.cleaned_data['tags'].split(',')]
+ set_sets(self._user, self._obj, tags)
+ return {"like": True}
+
+
+class NewSetForm(forms.Form):
+ name = forms.CharField(max_length=50, required=True)
+
+ def __init__(self, *args, **kwargs):
+ super(NewSetForm, self).__init__(*args, **kwargs)
+ self.fields['name'].widget.attrs['title'] = _('Name of the new shelf')
+
+ def save(self, user, commit=True):
+ name = self.cleaned_data['name']
+ new_set = Tag(name=name, slug=utils.get_random_hash(name), sort_key=name.lower(),
+ category='set', user=user)
+
+ new_set.save()
+ return new_set
--- /dev/null
+from django.db import models
+
+# Create your models here.
--- /dev/null
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags %}
+
+{% block titleextra %}{% trans "My shelf" %}{% endblock %}
+
+{% block logout %}/{% endblock %}
+
+{% block body %}
+
+ <h1>{% trans "My shelf" %}</h1>
+
+ {% work_list books %}
+
+{% endblock %}
--- /dev/null
+{% load i18n %}
+<h1>{{ title }}</h1>
+
+<form action="{% url social_unlike_book view_kwargs.slug %}" method="post" accept-charset="utf-8" class="cuteform">
+ <input type="submit" value="{% trans "Remove from my shelf" %}"/>
+</form>
+
+<form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8" class="cuteform">
+<ol>
+ <div id="id___all__"></div>
+ {{ form.as_ul }}
+ <li><input type="submit" value="{{ submit }}"/></li>
+</ol>
+</form>
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django import template
+from social.utils import likes
+
+register = template.Library()
+
+register.filter('likes', likes)
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf.urls.defaults import *
+from social.views import ObjectSetsFormView
+
+urlpatterns = patterns('social.views',
+ url(r'^lektura/(?P<slug>[a-z0-9-]+)/lubie/$', 'like_book', name='social_like_book'),
+ url(r'^lektura/(?P<slug>[a-z0-9-]+)/nie_lubie/$', 'unlike_book', name='social_unlike_book'),
+ url(r'^lektura/(?P<slug>[a-z0-9-]+)/polki/$', ObjectSetsFormView(), name='social_book_sets'),
+ url(r'^polka/$', 'my_shelf', name='social_my_shelf'),
+
+ #~ url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'),
+ #~ url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
+ #~ url(r'^polki/$', 'user_shelves', name='user_shelves'),
+ #~ url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
+ #~ url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'),
+ #~ url(r'^polki/nowa/$', 'new_set', name='new_set'),
+)
--- /dev/null
+from django.db.models import Q
+from catalogue.models import Tag
+from catalogue import utils
+from catalogue.tasks import touch_tag
+
+
+def likes(user, work):
+ return user.is_authenticated() and work.tags.filter(category='set', user=user).exists()
+
+
+def get_set(user, name):
+ """Returns a tag for use by the user. Creates it, if necessary."""
+ try:
+ tag = Tag.objects.get(category='set', user=user, name=name)
+ except Tag.DoesNotExist:
+ tag = Tag.objects.create(category='set', user=user, name=name,
+ slug=utils.get_random_hash(name), sort_key=name.lower())
+ return tag
+
+
+def set_sets(user, work, sets):
+ """Set tags used for given work by a given user."""
+
+ old_sets = list(work.tags.filter(category='set', user=user))
+
+ work.tags = sets + list(
+ work.tags.filter(~Q(category='set') | ~Q(user=user)))
+
+ for shelf in [shelf for shelf in old_sets if shelf not in sets]:
+ touch_tag(shelf)
+ for shelf in [shelf for shelf in sets if shelf not in old_sets]:
+ touch_tag(shelf)
+
+ # delete empty tags
+ Tag.objects.filter(category='set', user=user, book_count=0).delete()
--- /dev/null
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.shortcuts import render, get_object_or_404, redirect
+from django.http import HttpResponseForbidden
+from django.contrib.auth.decorators import login_required
+#~ from django.utils.datastructures import SortedDict
+from django.views.decorators.http import require_POST
+#~ from django.contrib import auth
+#~ from django.views.decorators import cache
+from django.utils.translation import ugettext as _
+
+from ajaxable.utils import LazyEncoder, JSONResponse, AjaxableFormView
+
+from catalogue.models import Book, Tag
+from social import forms
+from social.utils import get_set, likes, set_sets
+
+
+# ====================
+# = Shelf management =
+# ====================
+
+
+@require_POST
+def like_book(request, slug):
+ if not request.user.is_authenticated():
+ return HttpResponseForbidden('Login required.')
+ book = get_object_or_404(Book, slug=slug)
+ if not likes(request.user, book):
+ tag = get_set(request.user, '')
+ set_sets(request.user, book, [tag])
+
+ if request.is_ajax():
+ return JSONResponse({"success": True, "msg": "ok", "like": True})
+ else:
+ return redirect(book)
+
+
+@login_required
+def my_shelf(request):
+ books = Book.tagged.with_any(request.user.tag_set.all())
+ return render(request, 'social/my_shelf.html', locals())
+
+
+class ObjectSetsFormView(AjaxableFormView):
+ form_class = forms.ObjectSetsForm
+ template = 'social/sets_form.html'
+ ajax_redirect = True
+ POST_login = True
+
+ def form_args(self, request, slug):
+ book = get_object_or_404(Book, slug=slug)
+ return (book, request.user), {}
+
+
+def unlike_book(request, slug):
+ book = get_object_or_404(Book, slug=slug)
+ if likes(request.user, book):
+ set_sets(request.user, book, [])
+
+ if request.is_ajax():
+ return JSONResponse({"success": True, "msg": "ok", "like": False})
+ else:
+ return redirect(book)
+
+
+#~ @login_required
+#~ @cache.never_cache
+#~ def user_shelves(request):
+ #~ shelves = models.Tag.objects.filter(category='set', user=request.user)
+ #~ new_set_form = forms.NewSetForm()
+ #~ return render_to_response('social/user_shelves.html', locals(),
+ #~ context_instance=RequestContext(request))
+#~
+#~ @cache.never_cache
+#~ def book_sets(request, slug):
+ #~ if not request.user.is_authenticated():
+ #~ return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
+#~
+ #~ book = get_object_or_404(models.Book, slug=slug)
+#~
+ #~ user_sets = models.Tag.objects.filter(category='set', user=request.user)
+ #~ book_sets = book.tags.filter(category='set', user=request.user)
+#~
+ #~ if request.method == 'POST':
+ #~ form = forms.ObjectSetsForm(book, request.user, request.POST)
+ #~ if form.is_valid():
+ #~ DONE!
+ #~ if request.is_ajax():
+ #~ return JSONResponse('{"msg":"'+_("<p>Shelves were sucessfully saved.</p>")+'", "after":"close"}')
+ #~ else:
+ #~ return HttpResponseRedirect('/')
+ #~ else:
+ #~ form = forms.ObjectSetsForm(book, request.user)
+ #~ new_set_form = forms.NewSetForm()
+#~
+ #~ return render_to_response('social/book_sets.html', locals(),
+ #~ context_instance=RequestContext(request))
+#~
+#~
+#~ @login_required
+#~ @require_POST
+#~ @cache.never_cache
+#~ def remove_from_shelf(request, shelf, slug):
+ #~ book = get_object_or_404(models.Book, slug=slug)
+#~
+ #~ shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
+#~
+ #~ if shelf in book.tags:
+ #~ models.Tag.objects.remove_tag(book, shelf)
+ #~ touch_tag(shelf)
+#~
+ #~ return HttpResponse(_('Book was successfully removed from the shelf'))
+ #~ else:
+ #~ return HttpResponse(_('This book is not on the shelf'))
+#~
+#~
+#~ def collect_books(books):
+ #~ """
+ #~ Returns all real books in collection.
+ #~ """
+ #~ result = []
+ #~ for book in books:
+ #~ if len(book.children.all()) == 0:
+ #~ result.append(book)
+ #~ else:
+ #~ result += collect_books(book.children.all())
+ #~ return result
+#~
+#~
+#~ @cache.never_cache
+#~ def download_shelf(request, slug):
+ #~ """"
+ #~ Create a ZIP archive on disk and transmit it in chunks of 8KB,
+ #~ without loading the whole file into memory. A similar approach can
+ #~ be used for large dynamic PDF files.
+ #~ """
+ #~ from slughifi import slughifi
+ #~ import tempfile
+ #~ import zipfile
+#~
+ #~ shelf = get_object_or_404(models.Tag, slug=slug, category='set')
+#~
+ #~ formats = []
+ #~ form = forms.DownloadFormatsForm(request.GET)
+ #~ if form.is_valid():
+ #~ formats = form.cleaned_data['formats']
+ #~ if len(formats) == 0:
+ #~ formats = models.Book.ebook_formats
+#~
+ #~ # Create a ZIP archive
+ #~ temp = tempfile.TemporaryFile()
+ #~ archive = zipfile.ZipFile(temp, 'w')
+#~
+ #~ for book in collect_books(models.Book.tagged.with_all(shelf)):
+ #~ for ebook_format in models.Book.ebook_formats:
+ #~ if ebook_format in formats and book.has_media(ebook_format):
+ #~ filename = book.get_media(ebook_format).path
+ #~ archive.write(filename, str('%s.%s' % (book.slug, ebook_format)))
+ #~ archive.close()
+#~
+ #~ response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
+ #~ response['Content-Disposition'] = 'attachment; filename=%s.zip' % slughifi(shelf.name)
+ #~ response['Content-Length'] = temp.tell()
+#~
+ #~ temp.seek(0)
+ #~ response.write(temp.read())
+ #~ return response
+#~
+#~
+#~ @cache.never_cache
+#~ def shelf_book_formats(request, shelf):
+ #~ """"
+ #~ Returns a list of formats of books in shelf.
+ #~ """
+ #~ shelf = get_object_or_404(models.Tag, slug=shelf, category='set')
+#~
+ #~ formats = {}
+ #~ for ebook_format in models.Book.ebook_formats:
+ #~ formats[ebook_format] = False
+#~
+ #~ for book in collect_books(models.Book.tagged.with_all(shelf)):
+ #~ for ebook_format in models.Book.ebook_formats:
+ #~ if book.has_media(ebook_format):
+ #~ formats[ebook_format] = True
+#~
+ #~ return HttpResponse(LazyEncoder().encode(formats))
+#~
+#~
+#~ @login_required
+#~ @require_POST
+#~ @cache.never_cache
+#~ def new_set(request):
+ #~ new_set_form = forms.NewSetForm(request.POST)
+ #~ if new_set_form.is_valid():
+ #~ new_set = new_set_form.save(request.user)
+#~
+ #~ if request.is_ajax():
+ #~ return JSONResponse('{"id":"%d", "name":"%s", "msg":"<p>Shelf <strong>%s</strong> was successfully created</p>"}' % (new_set.id, new_set.name, new_set))
+ #~ else:
+ #~ return HttpResponseRedirect('/')
+#~
+ #~ return HttpResponseRedirect('/')
+#~
+#~
+#~ @login_required
+#~ @require_POST
+#~ @cache.never_cache
+#~ def delete_shelf(request, slug):
+ #~ user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
+ #~ user_set.delete()
+#~
+ #~ if request.is_ajax():
+ #~ return HttpResponse(_('<p>Shelf <strong>%s</strong> was successfully removed</p>') % user_set.name)
+ #~ else:
+ #~ return HttpResponseRedirect('/')
--- /dev/null
+django-debug-toolbar
django-rosetta>=0.5.3
django-maintenancemode>=0.9
django-piston
+django-jsonfield
python-memcached
piwik
'suggest',
'picture',
'search',
+ 'social',
]
CACHES = {
'127.0.0.1:11211',
]
},
+ 'permanent': {
+ 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+ 'TIMEOUT': 2419200,
+ 'LOCATION': [
+ '127.0.0.1:11211',
+ ]
+ },
'api': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': path.join(PROJECT_DIR, 'django_cache/'),
-.book-wide-box, .book-mini-box, .book-box {
+.book-mini-box, .Book-item {
display: inline-block;
+}
+
+.book-wide-box, .book-box {
margin: 0;
vertical-align: top;
}
.book-box-body {
height: 17em;
overflow: hidden;
+ position: relative;
}
.book-wide-box .book-box-body {
margin-left: 14em;
}
-.book-box-tools a.downarrow:before {
+.book-box-read a:before {
content: "\2609";
font-family: WL-Nav;
font-size: 2.25em;
margin-right: .15em;
vertical-align: middle;
+ font-weight: normal;
+}
+
+.book-box-download a:before {
+ content: "\21E9";
+ font-family: WL-Nav;
+ font-size: 2.25em;
+ margin-right: .15em;
+ vertical-align: middle;
+ font-weight: normal;
}
.book-box-audiobook a:before {
font-size: 2.25em;
margin-right: .15em;
vertical-align: middle;
+ font-weight: normal;
}
ul.book-box-tools {
margin: 6em 1.5em 0em 1.5em
}
+
+
+
+.star {
+ font-size: 2.25em;
+ margin-right: .5em;
+ position: absolute;
+ right: 0;
+}
+.star button::-moz-focus-inner {
+ padding: 0;
+ border: 0
+}
+.if-unlike button {
+ font-size: 1em;
+ font-family: inherit;
+ border: 0;
+ background: none;
+ margin: 0;
+ padding: 0;
+}
+
+.if-like a {
+ display:block;
+ text-align:right;
+ padding: 0;
+}
+
+.like .if-unlike {
+ display: none;
+}
+
+.unlike .if-like {
+ display: none;
+}
+
background-color: transparent;
margin-top: -0.5em;
margin-left: 1em;
+ width: 20em;
}
.dialog-window div.header {
$window.attr("id", this.id + "-window");
$('body').append($window);
+ var $trigger = $(this)
var trigger = '#' + this.id;
- var href = $(this).attr('href');
- if (href.search('\\?') != -1)
- href += '&ajax=1';
- else href += '?ajax=1';
-
$window.jqm({
- ajax: href,
+ ajax: '@href',
ajaxText: '<p><img src="' + STATIC_URL + 'img/indicator.gif" alt="*"/> ' + gettext("Loading") + '</p>',
target: $('.target', $window)[0],
overlay: 60,
onShow: function(hash) {
var offset = $(hash.t).offset();
hash.w.css({position: 'absolute', left: offset.left - hash.w.width() + $(hash.t).width(), top: offset.top});
- $('.header', hash.w).css({width: $(hash.t).width()});
+ var width = $(hash.t).width();
+ width = width > 50 ? width : 50;
+ $('.header', hash.w).css({width: width});
hash.w.show();
},
onLoad: function(hash) {
- $('form', hash.w).each(function() {
- if (this.action.search('[\\?&]ajax=1') != -1)
- return;
- if (this.action.search('\\?') != -1)
- this.action += '&ajax=1';
- else this.action += '?ajax=1';
- });
$('form', hash.w).ajaxForm({
dataType: 'json',
target: $('.target', $window),
if (response.success) {
$('.target', $window).text(response.message);
setTimeout(function() { $window.jqmHide() }, 1000);
+ callback = ajaxable_callbacks[$trigger.attr('data-callback')];
+ callback && callback($trigger, response);
if (response.redirect)
window.location = response.redirect;
}
});
+ var login_and_retry = function($form) {
+ var $window = $("#ajaxable-window").clone();
+ $window.attr("id", "context-login-window");
+ $('body').append($window);
+
+ $window.jqm({
+ ajax: '/uzytkownicy/zaloguj-utworz/',
+ ajaxText: '<p><img src="' + STATIC_URL + 'img/indicator.gif" alt="*"/> ' + gettext("Loading") + '</p>',
+ target: $('.target', $window)[0],
+ overlay: 60,
+ onShow: function(hash) {
+ var offset = $form.offset();
+ hash.w.css({position: 'absolute', left: offset.left - hash.w.width() + $form.width(), top: offset.top});
+ var width = $form.width();
+ width = width > 50 ? width : 50;
+ $('.header', hash.w).css({width: width});
+ hash.w.show();
+ },
+ onLoad: function(hash) {
+ $('form', hash.w).ajaxForm({
+ dataType: 'json',
+ target: $('.target', $window),
+ success: function(response) {
+ if (response.success) {
+ $('.target', $window).text(response.message);
+ setTimeout(function() { $window.jqmHide() }, 1000);
+ $form.submit();
+ }
+ else {
+ $('.error', $window).remove();
+ $.each(response.errors, function(id, errors) {
+ $('#id_' + id, $window).before('<span class="error">' + errors[0] + '</span>');
+ });
+ $('input[type=submit]', $window).removeAttr('disabled');
+ return false;
+ }
+ }
+ });
+ }
+ }).jqmShow();
+
+ };
+
+
+ $('.ajax-form').each(function() {
+ var $form = $(this);
+ $form.ajaxForm({
+ dataType: 'json',
+ beforeSubmit: function() {
+ $('input[type=submit]', $form)
+ .attr('disabled', 'disabled')
+ .after('<img src="/static/img/indicator.gif" style="margin-left: 0.5em"/>');
+ },
+ error: function(response) {
+ if (response.status == 403)
+ login_and_retry($form);
+ },
+ success: function(response) {
+ if (response.success) {
+ callback = ajax_form_callbacks[$form.attr('data-callback')];
+ callback && callback($form, response);
+
+ } else {
+ $('span.error', $form).remove();
+ $.each(response.errors, function(id, errors) {
+ $('#id_' + id, $form).before('<span class="error">' + errors[0] + '</span>');
+ });
+ $('input[type=submit]', $form).removeAttr('disabled');
+ $('img', $form).remove();
+ }
+ }
+ });
+ });
+
+
+ var update_star = function($elem, response) {
+ /* updates the star after successful ajax */
+ var $star = $elem.closest('.star');
+ if (response.like) {
+ $star.addClass('like');
+ $star.removeClass('unlike');
+ }
+ else {
+ $star.addClass('unlike');
+ $star.removeClass('like');
+ }
+ };
+
+ var ajax_form_callbacks = {
+ 'social-like-book': update_star
+ };
+
+ var ajaxable_callbacks = {
+ 'social-book-sets': update_star
+ };
+
+
});
})(jQuery)
--- /dev/null
+{% extends "ajaxable/form.html" %}
+{% load i18n %}
+
+{% block extra %}
+
+
+<h1>{% trans "or register" %}:</h1>
+
+<form action="{% url register %}" method="post" accept-charset="utf-8" class="cuteform">
+<ol>
+ <div id="id_register-__all__"></div>
+ {{ register_form.as_ul }}
+ <li><input type="submit" value="{{ register_submit }}"/></li>
+</ol>
+</form>
+
+
+{% endblock %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
- {% load i18n compressed catalogue_tags sponsor_tags %}
- {% load reporting_stats %}
+ {% load cache compressed i18n %}
+ {% load catalogue_tags reporting_stats sponsor_tags %}
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<div id="tagline">
<span>
- {% count_books book_count %}
- {% url book_list as b %}
- {% url book_list as r %}
- {% blocktrans count book_count as c %}
- <a href='{{b}}'>{{c}}</a> free reading you have <a href='{{r}}'>right to</a>
- {% plural %}
- <a href='{{b}}'>{{c}}</a> free readings you have <a href='{{r}}'>right to</a>
- {% endblocktrans %}
+ {% cache 300 tagline %}
+ {% url book_list as b %}
+ {% url book_list as r %}
+ {% count_books book_count %}
+ {% blocktrans count book_count as c %}
+ <a href='{{b}}'>{{c}}</a> free reading you have <a href='{{r}}'>right to</a>
+ {% plural %}
+ <a href='{{b}}'>{{c}}</a> free readings you have <a href='{{r}}'>right to</a>
+ {% endblocktrans %}
+ {% endcache %}
</span>
</div>
<p id="user-info" class="mono">
{% if user.is_authenticated %}
{% trans "Welcome" %}, <strong>{{ user.username }}</strong>
- | <a href="{% url user_shelves %}" id="user-shelves-link">{% trans "Your shelves" %}</a>
+ | <a href="{% url social_my_shelf %}" id="user-shelves-link">{% trans "My shelf" %}</a>
{% if user.is_staff %}
| <a href="/admin/">{% trans "Administration" %}</a>
{% endif %}
- | <a href="{% url logout %}?next={{ request.get_full_path }}">{% trans "Logout" %}</a>
+ | <a href="{% url logout %}?next={% block logout %}{{ request.get_full_path }}{% endblock %}">{% trans "Logout" %}</a>
{% else %}
<a href="{% url login %}?next={{ request.path }}"
id="login" class="ajaxable">
{% endthumbnail %}
" alt="Cover" />
{% endif %}
- {% for author in authors %}
- <div class="desc">
- <span class="mono author">{{ author }}</span>
- <span class="title">{{ book.title }}</span>
- </div>
- {% endfor %}
+ <div class="desc">
+ <span class="mono author">
+ {% for name, url in related.tags.author %}
+ {{ name }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+ </span>
+ <span class="title">{{ book.title }}</span>
+ </div>
</a>
</div>
{% load i18n %}
+{% load social_tags %}
{% load thumbnail %}
<div class="{% block box-class %}book-box{% endblock %}">
-<div class="book-box-inner">
+<div class="book-box-inner" style="position: relative;">
+
+
<a href="{{ book.get_absolute_url }}">
{% if book.cover %}
<img src="
{% block right-column %}
{% endblock %}
<div class="book-box-body">
+
+
+<div class="star {% if not request.user|likes:book %}un{% endif %}like">
+ <div class="if-like" >
+ <a id="social-book-sets-{{ book.slug }}" data-callback='social-book-sets' class='ajaxable' href='{% url social_book_sets book.slug %}'>
+ ★
+ </a>
+ </div>
+ <div class="if-unlike">
+ <form id="social-like-book-{{ book.slug }}" data-callback='social-like-book' method='post' class='ajax-form' action='{% url social_like_book book.slug %}'>
+ <button type='submit'>☆</button>
+ </form>
+ </div>
+</div>
+
+
<div class="book-box-head">
<div class="mono author">
- {% for author in tags.author %}
- {{ author }}
- {% endfor %}
+ {% for name, url in related.tags.author %}
+ {{ name }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
</div>
<div class="title">{{ book.title }}</div>
</div>
{% spaceless %}
<span class="mono">{% trans "Epoch" %}:</span> <span class="book-box-tag">
- {% for tag in tags.epoch %}
- <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+ {% for name, url in related.tags.epoch %}
+ <a href="{{ url }}">{{ name }} </a>
{% endfor %}
</span>
<span class="mono">{% trans "Kind" %}:</span> <span class="book-box-tag">
- {% for tag in tags.kind %}
- <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+ {% for name, url in related.tags.kind %}
+ <a href="{{ url }}">{{ name }} </a>
{% endfor %}
</span>
<span class="mono">{% trans "Genre" %}:</span> <span class="book-box-tag">
- {% for tag in tags.genre %}
- <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+ {% for name, url in related.tags.genre %}
+ <a href="{{ url }}">{{ name }} </a>
{% endfor %}
</span>
</div>
</li>
<li class="book-box-audiobook">
- {% if book.has_mp3_file %}
+ {% if related.media.mp3 or related.media.ogg %}
<a href="{% url book_player book.slug %}" class="open-player mono downarrow">{% trans "Listen" %}</a>
{% endif %}
</li>
Gdy długo spoglądamy w otchłań, otchłań spogląda również w nas.</div>
</blockquote>
+
<div id="other-tools">
<h2 class="mono">{% trans "See" %}</h2>
<ul class="inline-items">
<h2 class="mono">{% trans "Download" %}</h2>
<ul class="inline-items">
<li>
- {% if has_media.mp3 or has_media.ogg %}
+ {% if related.media.mp3 or related.media.ogg %}
{% trans "Download all audiobooks for this book" %}:
- {% if has_media.mp3 %}<a href="{% url download_zip_mp3 book.slug %}">MP3</a>{% endif %}{% if has_media.mp4 and has_media.ogg %},{% endif %}
- {% if has_media.ogg %}<a href="{% url download_zip_ogg book.slug %}">OGG</a>{% endif %}.
+ {% if related.media.mp3 %}<a href="{% url download_zip_mp3 book.slug %}">MP3</a>{% endif %}{% if related.media.mp3 and related.media.ogg %},{% endif %}
+ {% if related.media.ogg %}<a href="{% url download_zip_ogg book.slug %}">OGG</a>{% endif %}.
{% endif %}
</li>
<li>
{% extends "base.html" %}
{% load i18n %}
-{% load catalogue_tags pagination_tags switch_tag %}
+{% load catalogue_tags switch_tag %}
{% block titleextra %}{% title_from_tags tags %}{% endblock %}
- {% autopaginate object_list 10 %}
<div id="books-list">
{% endif %}
{% if object_list %}
- {% spaceless %}
- <ol class='work-list'>
- {% for book in object_list %}
- <li class='work-item'>
- {% if user_is_owner %}
- <a href="{% url remove_from_shelf last_tag.slug book.slug %}" class="remove-from-shelf">{% trans "Delete" %}</a>
- {% endif %}
- {{ book.short_html }}</li>
- {% endfor %}
- </ol>
- {% endspaceless %}
- {% paginate %}
+ {% work_list object_list %}
{% else %}
{% trans "Sorry! Search cirteria did not match any resources." %}
{% include "info/join_us.html" %}
{% endif %}
{% endwith %}
</div>
- {% if object_list %}
- {% comment %} If we didn't find anything there will be nothing on the right side as well {% endcomment %}
- {% endif %}
{% endblock %}
--- /dev/null
+{% load pagination_tags %}
+{% load book_short from catalogue_tags %}
+
+{% autopaginate object_list 10 %}
+{% spaceless %}
+<ol class='work-list'>
+{% for item in object_list %}
+ <li class='{{ object_type }}-item'>
+ {% if object_type == 'Book' %}
+ {% book_short item %}
+ {% else %}
+ {{ item.short_html }}
+ {% endif %}
+ </li>
+{% endfor %}
+</ol>
+{% endspaceless %}
+{% paginate %}
\ No newline at end of file
<h2 class="main-last"><span class="mono">Ostatnie publikacje</span></h2>
- {% for book in last_published %}
- {{ book.mini_box }}
- {% endfor %}
+ {% cache 300 last-published-on-main %}
+ {% for book in last_published %}
+ {% book_mini book %}
+ {% endfor %}
+ {% endcache %}
<div class="clearboth"></div>
<div class="infopages-box">
<h2><span class='mono'>Informacje</span></h2>
-
- {% infopages_on_main %}
+ {% cache 300 infopages-on-main LANGUAGE_CODE %}
+ {% infopages_on_main %}
+ {% endcache %}
<div class="social-links">
<a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"><img src="{{ STATIC_URL }}img/social/facebook.png" alt="WolneLektury @ Facebook" /></a>
url(r'^uzytkownicy/zaloguj/$', views.LoginFormView(), name='login'),
url(r'^uzytkownicy/utworz/$', views.RegisterFormView(), name='register'),
url(r'^uzytkownicy/wyloguj/$', 'logout_then_redirect', name='logout'),
+ url(r'^uzytkownicy/zaloguj-utworz/$', views.LoginRegisterFormView(), name='login_register'),
)
urlpatterns += patterns('',
url(r'^przypisy/', include('dictionary.urls')),
url(r'^raporty/', include('reporting.urls')),
url(r'^info/', include('infopages.urls')),
+ url(r'^ludzie/', include('social.urls')),
# Admin panel
url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
title = _('Register')
submit = _('Register')
ajax_redirect = True
+ form_prefix = 'register'
def __call__(self, request):
if request.user.is_authenticated():
auth.login(request, user)
+class LoginRegisterFormView(LoginFormView):
+ template = 'auth/login_register.html'
+
+ def extra_context(self):
+ return {
+ "register_form": UserCreationForm(prefix='register'),
+ "register_submit": _('Register'),
+ }
+
+
@never_cache
def logout_then_redirect(request):
auth.logout(request)