From: Łukasz Rekucki Date: Tue, 15 Jun 2010 23:50:00 +0000 (+0200) Subject: - Added librarian as a submodule. X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/9c5d9a4e77a10b4e60d89d3890e49002bd7f3993?ds=sidebyside - Added librarian as a submodule. - Split tests in catalouge into a package. - Don't write to actual MEDIA_ROOT during tests. - Fixed trailing whitespace. --- diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..56b9c4254 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/librarian"] + path = lib/librarian + url = git://github.com/fnp/librarian.git diff --git a/apps/api/handlers.py b/apps/api/handlers.py index 799b60827..40121d6af 100644 --- a/apps/api/handlers.py +++ b/apps/api/handlers.py @@ -16,14 +16,14 @@ staff_required = user_passes_test(lambda user: user.is_staff) class BookHandler(BaseHandler): model = Book fields = ('slug', 'title') - + @staff_required def read(self, request, slug=None): if slug: return get_object_or_404(Book, slug=slug) else: return Book.objects.all() - + @staff_required def create(self, request): form = BookImportForm(request.POST, request.FILES) diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py index b2744ee70..02750941c 100644 --- a/apps/catalogue/admin.py +++ b/apps/catalogue/admin.py @@ -20,7 +20,7 @@ class TagAdmin(admin.ModelAdmin): class BookAdmin(TaggableModelAdmin): tag_model = Tag - + list_display = ('title', 'slug', 'has_pdf_file', 'has_epub_file', 'has_odt_file', 'has_html_file', 'has_description',) search_fields = ('title',) ordering = ('title',) @@ -30,14 +30,14 @@ class BookAdmin(TaggableModelAdmin): class FragmentAdmin(TaggableModelAdmin): tag_model = Tag - + list_display = ('book', 'anchor',) ordering = ('book', 'anchor',) class BookStubAdmin(admin.ModelAdmin): # tag_model = Tag - + list_display = ('title', 'author', 'slug','pd') search_fields = ('title','author') ordering = ('title',) diff --git a/apps/catalogue/fields.py b/apps/catalogue/fields.py index ded6f0e93..62ca29cb9 100644 --- a/apps/catalogue/fields.py +++ b/apps/catalogue/fields.py @@ -123,7 +123,7 @@ try: ( [JSONField], # Class(es) these apply to [], # Positional arguments (not used) - {}, # Keyword argument + {}, # Keyword argument ), ], ["^catalogue\.fields\.JSONField"]) except ImportError: pass diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 60a99cdf8..d2178248e 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -21,7 +21,7 @@ class BookImportForm(forms.Form): class SearchForm(forms.Form): q = JQueryAutoCompleteField('/katalog/tags/', {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"}) tags = forms.CharField(widget=forms.HiddenInput, required=False) - + def __init__(self, *args, **kwargs): tags = kwargs.pop('tags', []) super(SearchForm, self).__init__(*args, **kwargs) @@ -39,7 +39,7 @@ class UserSetsForm(forms.Form): class ObjectSetsForm(forms.Form): - def __init__(self, obj, user, *args, **kwargs): + def __init__(self, obj, user, *args, **kwargs): super(ObjectSetsForm, self).__init__(*args, **kwargs) self.fields['set_ids'] = forms.MultipleChoiceField( label=_('Shelves'), @@ -48,20 +48,20 @@ class ObjectSetsForm(forms.Form): 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=slughifi(name), category='set', user=user) - + new_set.save() return new_set @@ -78,7 +78,7 @@ FORMATS = ( class DownloadFormatsForm(forms.Form): formats = forms.MultipleChoiceField(required=False, choices=FORMATS, widget=forms.CheckboxSelectMultiple) - + def __init__(self, *args, **kwargs): super(DownloadFormatsForm, self).__init__(*args, **kwargs) diff --git a/apps/catalogue/management/commands/importbooks.py b/apps/catalogue/management/commands/importbooks.py index c5fbb2e82..cead75fa2 100644 --- a/apps/catalogue/management/commands/importbooks.py +++ b/apps/catalogue/management/commands/importbooks.py @@ -39,7 +39,7 @@ class Command(BaseCommand): files_imported = 0 files_skipped = 0 - + for dir_name in directories: if not os.path.isdir(dir_name): print self.style.ERROR("%s: Not a directory. Skipping." % dir_name) @@ -47,30 +47,30 @@ class Command(BaseCommand): for file_name in os.listdir(dir_name): file_path = os.path.join(dir_name, file_name) file_base, ext = os.path.splitext(file_path) - + # Skip files that are not XML files if not ext == '.xml': continue - + if verbose > 0: print "Parsing '%s'" % file_path else: sys.stdout.write('.') sys.stdout.flush() - + # Import book files try: book = Book.from_xml_file(file_path, overwrite=force) files_imported += 1 - + if os.path.isfile(file_base + '.pdf'): book.pdf_file.save('%s.pdf' % book.slug, File(file(file_base + '.pdf'))) if verbose: - print "Importing %s.pdf" % file_base + print "Importing %s.pdf" % file_base if os.path.isfile(file_base + '.epub'): book.epub_file.save('%s.epub' % book.slug, File(file(file_base + '.epub'))) if verbose: - print "Importing %s.epub" % file_base + print "Importing %s.epub" % file_base if os.path.isfile(file_base + '.odt'): book.odt_file.save('%s.odt' % book.slug, File(file(file_base + '.odt'))) if verbose: @@ -87,20 +87,20 @@ class Command(BaseCommand): book.ogg_file.save('%s.ogg' % book.slug, File(file(os.path.join(dir_name, book.slug + '.ogg')))) if verbose: print "Importing %s.ogg" % book.slug - + book.save() - + except Book.AlreadyExists, msg: print self.style.ERROR('%s: Book already imported. Skipping. To overwrite use --force.' % file_path) files_skipped += 1 - + # Print results print print "Results: %d files imported, %d skipped, %d total." % ( files_imported, files_skipped, files_imported + files_skipped) print - + transaction.commit() transaction.leave_transaction_management() diff --git a/apps/catalogue/migrations/0001_initial.py b/apps/catalogue/migrations/0001_initial.py index 05ba18d1e..2aabb8237 100644 --- a/apps/catalogue/migrations/0001_initial.py +++ b/apps/catalogue/migrations/0001_initial.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'Tag' db.create_table('catalogue_tag', ( ('category', self.gf('django.db.models.fields.CharField')(max_length=50, db_index=True)), @@ -69,10 +69,10 @@ class Migration(SchemaMigration): ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), )) db.send_create_signal('catalogue', ['Fragment']) - - + + def backwards(self, orm): - + # Deleting model 'Tag' db.delete_table('catalogue_tag') @@ -87,8 +87,8 @@ class Migration(SchemaMigration): # Deleting model 'Fragment' db.delete_table('catalogue_fragment') - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -178,5 +178,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0002_auto__add_bookstub__add_field_tag_death.py b/apps/catalogue/migrations/0002_auto__add_bookstub__add_field_tag_death.py index 508f95726..c278a83a0 100644 --- a/apps/catalogue/migrations/0002_auto__add_bookstub__add_field_tag_death.py +++ b/apps/catalogue/migrations/0002_auto__add_bookstub__add_field_tag_death.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'BookStub' db.create_table('catalogue_bookstub', ( ('author', self.gf('django.db.models.fields.CharField')(max_length=120)), @@ -22,17 +22,17 @@ class Migration(SchemaMigration): # Adding field 'Tag.death' db.add_column('catalogue_tag', 'death', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) - - + + def backwards(self, orm): - + # Deleting model 'BookStub' db.delete_table('catalogue_bookstub') # Deleting field 'Tag.death' db.delete_column('catalogue_tag', 'death') - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -133,5 +133,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0003_fix_book_count_on_shelves.py b/apps/catalogue/migrations/0003_fix_book_count_on_shelves.py index a161e277b..8180311b2 100644 --- a/apps/catalogue/migrations/0003_fix_book_count_on_shelves.py +++ b/apps/catalogue/migrations/0003_fix_book_count_on_shelves.py @@ -23,11 +23,11 @@ class Migration(DataMigration): 'tagged_item': qn(orm.TagRelation._meta.db_table), 'tag_id': tag.pk, } - + cursor = connection.cursor() cursor.execute(query) book_count = (cursor.fetchone() or (0,))[0] - + tag.book_count = book_count tag.save() diff --git a/apps/catalogue/migrations/0004_book_html_shorts_translations.py b/apps/catalogue/migrations/0004_book_html_shorts_translations.py index 8d8e06af2..d9e7b5303 100644 --- a/apps/catalogue/migrations/0004_book_html_shorts_translations.py +++ b/apps/catalogue/migrations/0004_book_html_shorts_translations.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding field 'Book._short_html_en' db.add_column('catalogue_book', '_short_html_en', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) @@ -31,10 +31,10 @@ class Migration(SchemaMigration): # Adding field 'Book._short_html_lt' db.add_column('catalogue_book', '_short_html_lt', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) - - + + def backwards(self, orm): - + # Deleting field 'Book._short_html_en' db.delete_column('catalogue_book', '_short_html_en') @@ -58,8 +58,8 @@ class Migration(SchemaMigration): # Deleting field 'Book._short_html_lt' db.delete_column('catalogue_book', '_short_html_lt') - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -168,5 +168,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0005_fragment_html_shorts_translations.py b/apps/catalogue/migrations/0005_fragment_html_shorts_translations.py index 4828d6f6c..8322b4c24 100644 --- a/apps/catalogue/migrations/0005_fragment_html_shorts_translations.py +++ b/apps/catalogue/migrations/0005_fragment_html_shorts_translations.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding field 'Fragment._short_html_de' db.add_column('catalogue_fragment', '_short_html_de', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) @@ -31,10 +31,10 @@ class Migration(SchemaMigration): # Adding field 'Fragment._short_html_uk' db.add_column('catalogue_fragment', '_short_html_uk', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) - - + + def backwards(self, orm): - + # Deleting field 'Fragment._short_html_de' db.delete_column('catalogue_fragment', '_short_html_de') @@ -58,8 +58,8 @@ class Migration(SchemaMigration): # Deleting field 'Fragment._short_html_uk' db.delete_column('catalogue_fragment', '_short_html_uk') - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -176,5 +176,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0006_epub_tag_counters_and_ltags_descendants.py b/apps/catalogue/migrations/0006_epub_tag_counters_and_ltags_descendants.py index 7b2f2c517..b990fc33f 100644 --- a/apps/catalogue/migrations/0006_epub_tag_counters_and_ltags_descendants.py +++ b/apps/catalogue/migrations/0006_epub_tag_counters_and_ltags_descendants.py @@ -14,7 +14,7 @@ def get_ltag(book, orm): class Migration(SchemaMigration): - + def forwards(self, orm): """ Add _tag_counter and make sure all books carry their ancestors' l-tags """ @@ -32,7 +32,7 @@ class Migration(SchemaMigration): ltag = get_ltag(book, orm) for child in book.children.all(): ltag_descendants(child, ltags + [ltag]) - + if not db.dry_run: try: book_ct = orm['contenttypes.contenttype'].objects.get(app_label='catalogue', model='book') @@ -42,8 +42,8 @@ class Migration(SchemaMigration): orm.TagRelation.objects.filter(content_type=book_ct, tag__category='book').delete() for book in orm.Book.objects.filter(parent=None): ltag_descendants(book) - - + + def backwards(self, orm): """ Delete _tag_counter and make sure books carry own l-tag. """ @@ -61,8 +61,8 @@ class Migration(SchemaMigration): orm.TagRelation.objects.filter(content_type=book_ct, tag__category='book').delete() for book in orm.Book.objects.filter(parent=None): orm.TagRelation(object_id=book.pk, tag=get_ltag(book, orm), content_type=book_ct).save() - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -182,5 +182,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0007_remove_empty_html.py b/apps/catalogue/migrations/0007_remove_empty_html.py index ca87ccb8b..5b6f45393 100644 --- a/apps/catalogue/migrations/0007_remove_empty_html.py +++ b/apps/catalogue/migrations/0007_remove_empty_html.py @@ -5,12 +5,12 @@ from south.v2 import DataMigration from django.db import models class Migration(DataMigration): - + def forwards(self, orm): """ Look for HTML files without any real content and delete them """ from lxml import etree from librarian.html import html_has_content - + for book in orm.Book.objects.exclude(html_file=''): if not html_has_content(etree.parse(book.html_file)): book.html_file.delete() @@ -18,11 +18,11 @@ class Migration(DataMigration): for key in filter(lambda x: x.startswith('_short_html'), book.__dict__): book.__setattr__(key, '') book.save() - + def backwards(self, orm): """ Do nothing. We don't want empty HTML files anyway. """ pass - + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -142,5 +142,5 @@ class Migration(DataMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0008_unique_tag_category_slug.py b/apps/catalogue/migrations/0008_unique_tag_category_slug.py index 876d0fda5..d13081959 100644 --- a/apps/catalogue/migrations/0008_unique_tag_category_slug.py +++ b/apps/catalogue/migrations/0008_unique_tag_category_slug.py @@ -5,25 +5,25 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Removing unique constraint on 'Tag', fields ['slug'] db.delete_unique('catalogue_tag', ['slug']) # Adding unique constraint on 'Tag', fields ['category', 'slug'] db.create_unique('catalogue_tag', ['category', 'slug']) - - + + def backwards(self, orm): - + # Adding unique constraint on 'Tag', fields ['slug'] db.create_unique('catalogue_tag', ['slug']) # Removing unique constraint on 'Tag', fields ['category', 'slug'] db.delete_unique('catalogue_tag', ['category', 'slug']) - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -143,5 +143,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) } } - + complete_apps = ['catalogue'] diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 2008f0970..367f382c6 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -55,7 +55,7 @@ class Tag(TagBase): death = models.IntegerField(_(u'year of death'), blank=True, null=True) gazeta_link = models.CharField(blank=True, max_length=240) wiki_link = models.CharField(blank=True, max_length=240) - + categories_rev = { 'autor': 'author', 'epoka': 'epoch', @@ -117,7 +117,7 @@ class Tag(TagBase): real_tags.append(Tag.objects.exclude(category='book').get(slug=name)) except Tag.MultipleObjectsReturned, e: ambiguous_slugs.append(name) - + if category: # something strange left off raise Tag.DoesNotExist() @@ -131,13 +131,13 @@ class Tag(TagBase): return real_tags else: return TagBase.get_tag_list(tags) - + @property def url_chunk(self): return '/'.join((Tag.categories_dict[self.category], self.slug)) -# TODO: why is this hard-coded ? +# TODO: why is this hard-coded ? def book_upload_path(ext): def get_dynamic_path(book, filename): return 'lektura/%s.%s' % (book.slug, ext) @@ -171,7 +171,7 @@ class Book(models.Model): objects = models.Manager() tagged = managers.ModelTaggedItemManager(Tag) tags = managers.TagDescriptor(Tag) - + _tag_counter = JSONField(null=True, editable=False) _theme_counter = JSONField(null=True, editable=False) @@ -210,7 +210,7 @@ class Book(models.Model): @property def name(self): return self.title - + def book_tag(self): slug = ('l-' + self.slug)[:120] book_tag, created = Tag.objects.get_or_create(slug=slug, category='book') @@ -399,8 +399,8 @@ class Book(models.Model): book.save() return book - - + + def refresh_tag_counter(self): tags = {} for child in self.children.all().order_by(): @@ -411,7 +411,7 @@ class Book(models.Model): self.set__tag_counter_value(tags) self.save(reset_short_html=False, refresh_mp3=False) return tags - + @property def tag_counter(self): if self._tag_counter is None: @@ -426,13 +426,13 @@ class Book(models.Model): self.set__theme_counter_value(tags) self.save(reset_short_html=False, refresh_mp3=False) return tags - + @property def theme_counter(self): if self._theme_counter is None: return self.refresh_theme_counter() return dict((int(k), v) for k, v in self.get__theme_counter_value().iteritems()) - + class Fragment(models.Model): diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py index 504ee69d7..7f2bf291d 100644 --- a/apps/catalogue/templatetags/catalogue_tags.py +++ b/apps/catalogue/templatetags/catalogue_tags.py @@ -54,7 +54,7 @@ def simple_title(tags): 'kind': u'rodzaj', 'set': u'półka', } - + title = [] for tag in tags: title.append("%s: %s" % (mapping[tag.category], tag.name)) @@ -68,46 +68,46 @@ def title_from_tags(tags): for tag in tags: result[tag.category] = tag return result - + # TODO: Remove this after adding flection mechanism return simple_title(tags) - + class Flection(object): def get_case(self, name, flection): return name flection = Flection() - + self = split_tags(tags) - + title = u'' - + # Specjalny przypadek oglądania wszystkich lektur na danej półce if len(self) == 1 and 'set' in self: return u'Półka %s' % self['set'] - + # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka # jest wybrana przez użytkownika if 'epoch' in self and len(self) == 1: text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik') return capfirst(text) - + # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane # są tylko rodzaj literacki i autor if 'kind' in self and 'author' in self and len(self) == 2: - text = u'%s w twórczości %s' % (unicode(self['kind']), + text = u'%s w twórczości %s' % (unicode(self['kind']), flection.get_case(unicode(self['author']), u'dopełniacz')) return capfirst(text) - + # Przypadki ogólniejsze if 'theme' in self: title += u'Motyw %s' % unicode(self['theme']) - + if 'genre' in self: if 'theme' in self: title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik') else: title += unicode(self['genre']) - + if 'kind' in self or 'author' in self or 'epoch' in self: if 'genre' in self or 'theme' in self: if 'kind' in self: @@ -116,12 +116,12 @@ def title_from_tags(tags): title += u' w twórczości ' else: title += u'%s ' % unicode(self.get('kind', u'twórczość')) - + if 'author' in self: title += flection.get_case(unicode(self['author']), u'dopełniacz') elif 'epoch' in self: title += flection.get_case(unicode(self['epoch']), u'dopełniacz') - + return capfirst(title) @@ -152,7 +152,7 @@ def breadcrumbs(tags, search_form=True): def catalogue_url(parser, token): bits = token.split_contents() tag_name = bits[0] - + tags_to_add = [] tags_to_remove = [] for bit in bits[1:]: @@ -160,7 +160,7 @@ def catalogue_url(parser, token): tags_to_remove.append(bit[1:]) else: tags_to_add.append(bit) - + return CatalogueURLNode(tags_to_add, tags_to_remove) @@ -168,7 +168,7 @@ class CatalogueURLNode(Node): def __init__(self, tags_to_add, tags_to_remove): self.tags_to_add = [Variable(tag) for tag in tags_to_add] self.tags_to_remove = [Variable(tag) for tag in tags_to_remove] - + def render(self, context): tags_to_add = [] tags_to_remove = [] @@ -186,14 +186,14 @@ class CatalogueURLNode(Node): tags_to_remove += [t for t in tag] else: tags_to_remove.append(tag) - + tag_slugs = [tag.url_chunk for tag in tags_to_add] for tag in tags_to_remove: try: tag_slugs.remove(tag.url_chunk) except KeyError: pass - + if len(tag_slugs) > 0: return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)}) else: @@ -201,7 +201,7 @@ class CatalogueURLNode(Node): @register.inclusion_tag('catalogue/latest_blog_posts.html') -def latest_blog_posts(feed_url, posts_to_show=5): +def latest_blog_posts(feed_url, posts_to_show=5): try: feed = feedparser.parse(str(feed_url)) posts = [] @@ -234,7 +234,7 @@ def folded_tag_list(tags, choices=None): choices = [] some_tags_hidden = False tag_count = len(tags) - + if tag_count == 1: one_tag = tags[0] else: diff --git a/apps/catalogue/templatetags/switch_tag.py b/apps/catalogue/templatetags/switch_tag.py index b91f872a4..72476bef0 100644 --- a/apps/catalogue/templatetags/switch_tag.py +++ b/apps/catalogue/templatetags/switch_tag.py @@ -1,11 +1,11 @@ # Source: http://djangosnippets.org/snippets/967/ # Author: adurdin # Posted: August 13, 2008 -# -# +# +# # We can use it based on djangosnippets Terms of Service: # (http://djangosnippets.org/about/tos/) -# +# # 2. That you grant any third party who sees the code you post # a royalty-free, non-exclusive license to copy and distribute that code # and to make and distribute derivative works based on that code. You may @@ -71,13 +71,13 @@ def do_switch(parser, token): got_else = False while token.contents != 'endswitch': nodelist = parser.parse(BlockTagList('case', 'else', 'endswitch')) - + if got_else: raise template.TemplateSyntaxError("'else' must be last tag in '%s'." % tag_name) contents = token.contents.split() token_name, token_args = contents[0], contents[1:] - + if token_name == 'case': tests = map(parser.compile_filter, token_args) case = (tests, nodelist) @@ -122,7 +122,7 @@ class SwitchNode(Node): except VariableDoesNotExist: no_value = True value_missing = None - + for tests, nodelist in self.cases: if tests is None: return nodelist.render(context) diff --git a/apps/catalogue/test_utils.py b/apps/catalogue/test_utils.py new file mode 100644 index 000000000..3a8af57aa --- /dev/null +++ b/apps/catalogue/test_utils.py @@ -0,0 +1,38 @@ +from django.conf import settings +from django.test import TestCase +import shutil +import tempfile + +class WLTestCase(TestCase): + """ + Generic base class for tests. Adds settings freeze and clears MEDIA_ROOT. + """ + def setUp(self): + self._MEDIA_ROOT, settings.MEDIA_ROOT = settings.MEDIA_ROOT, tempfile.mkdtemp(prefix='djangotest_') + + def tearDown(self): + shutil.rmtree(settings.MEDIA_ROOT, True) + settings.MEDIA_ROOT = self._MEDIA_ROOT + +class PersonStub(object): + + def __init__(self, first_names, last_name): + self.first_names = first_names + self.last_name = last_name + + +class BookInfoStub(object): + + def __init__(self, **kwargs): + self.__dict = kwargs + + def __setattr__(self, key, value): + if not key.startswith('_'): + self.__dict[key] = value + return object.__setattr__(self, key, value) + + def __getattr__(self, key): + return self.__dict[key] + + def to_dict(self): + return dict((key, unicode(value)) for key, value in self.__dict.items()) diff --git a/apps/catalogue/tests.py b/apps/catalogue/tests.py deleted file mode 100644 index 829f8dce2..000000000 --- a/apps/catalogue/tests.py +++ /dev/null @@ -1,476 +0,0 @@ -# -*- coding: utf-8 -*- -from django.test import TestCase -from catalogue import models, views -from django.core.files.base import ContentFile -from django.contrib.auth.models import User, AnonymousUser -from django.test.client import Client - -from nose.tools import raises -from StringIO import StringIO - -class BasicSearchLogicTests(TestCase): - - def setUp(self): - self.author_tag = models.Tag.objects.create( - name=u'Adam Mickiewicz [SubWord]', - category=u'author', slug="one") - - self.unicode_tag = models.Tag.objects.create( - name=u'Tadeusz Å»eleński (Boy)', - category=u'author', slug="two") - - self.polish_tag = models.Tag.objects.create( - name=u'ĘÓĄŚŁŻŹĆŃęóąśłżźćń', - category=u'author', slug="three") - - @raises(ValueError) - def test_empty_query(self): - """ Check that empty queries raise an error. """ - views.find_best_matches(u'') - - @raises(ValueError) - def test_one_letter_query(self): - """ Check that one letter queries aren't permitted. """ - views.find_best_matches(u't') - - def test_match_by_prefix(self): - """ Tags should be matched by prefix of words within it's name. """ - self.assertEqual(views.find_best_matches(u'Ada'), (self.author_tag,)) - self.assertEqual(views.find_best_matches(u'Mic'), (self.author_tag,)) - self.assertEqual(views.find_best_matches(u'Mickiewicz'), (self.author_tag,)) - - def test_match_case_insensitive(self): - """ Tag names should match case insensitive. """ - self.assertEqual(views.find_best_matches(u'adam mickiewicz'), (self.author_tag,)) - - def test_match_case_insensitive_unicode(self): - """ Tag names should match case insensitive (unicode). """ - self.assertEqual(views.find_best_matches(u'tadeusz żeleński (boy)'), (self.unicode_tag,)) - - def test_word_boundary(self): - self.assertEqual(views.find_best_matches(u'SubWord'), (self.author_tag,)) - self.assertEqual(views.find_best_matches(u'[SubWord'), (self.author_tag,)) - - def test_unrelated_search(self): - self.assertEqual(views.find_best_matches(u'alamakota'), tuple()) - self.assertEqual(views.find_best_matches(u'Adama'), ()) - - def test_infix_doesnt_match(self): - """ Searching for middle of a word shouldn't match. """ - self.assertEqual(views.find_best_matches(u'deusz'), tuple()) - - def test_diactricts_removal_pl(self): - """ Tags should match both with and without national characters. """ - self.assertEqual(views.find_best_matches(u'ĘÓĄŚŁŻŹĆŃęóąśłżźćń'), (self.polish_tag,)) - self.assertEqual(views.find_best_matches(u'EOASLZZCNeoaslzzcn'), (self.polish_tag,)) - self.assertEqual(views.find_best_matches(u'eoaslzzcneoaslzzcn'), (self.polish_tag,)) - - def test_diactricts_query_removal_pl(self): - """ Tags without national characters shouldn't be matched by queries with them. """ - self.assertEqual(views.find_best_matches(u'Adąm'), ()) - - def test_sloppy(self): - self.assertEqual(views.find_best_matches(u'Å»elenski'), (self.unicode_tag,)) - self.assertEqual(views.find_best_matches(u'zelenski'), (self.unicode_tag,)) - - -class PersonStub(object): - - def __init__(self, first_names, last_name): - self.first_names = first_names - self.last_name = last_name - -from slughifi import slughifi - -class BookInfoStub(object): - - def __init__(self, **kwargs): - self.__dict = kwargs - - def __setattr__(self, key, value): - if not key.startswith('_'): - self.__dict[key] = value - return object.__setattr__(self, key, value) - - def __getattr__(self, key): - return self.__dict[key] - - def to_dict(self): - return dict((key, unicode(value)) for key, value in self.__dict.items()) - -def info_args(title): - """ generate some keywords for comfortable BookInfoCreation """ - slug = unicode(slughifi(title)) - return {'title': unicode(title), - 'slug': slug, - 'url': u"http://wolnelektury.pl/example/%s" % slug, - 'about': u"http://wolnelektury.pl/example/URI/%s" % slug, - } - -class BookImportLogicTests(TestCase): - - def setUp(self): - self.book_info = BookInfoStub( - url=u"http://wolnelektury.pl/example/default_book", - about=u"http://wolnelektury.pl/example/URI/default_book", - title=u"Default Book", - author=PersonStub(("Jim",), "Lazy"), - kind="X-Kind", - genre="X-Genre", - epoch="X-Epoch", - ) - - self.expected_tags = [ - ('author', 'jim-lazy'), - ('genre', 'x-genre'), - ('epoch', 'x-epoch'), - ('kind', 'x-kind'), - ] - self.expected_tags.sort() - - def tearDown(self): - models.Book.objects.all().delete() - - def test_empty_book(self): - BOOK_TEXT = "" - book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) - - self.assertEqual(book.title, "Default Book") - self.assertEqual(book.slug, "default_book") - self.assert_(book.parent is None) - self.assertFalse(book.has_html_file()) - - # no fragments generated - self.assertEqual(book.fragments.count(), 0) - - # TODO: this should be filled out probably... - self.assertEqual(book.wiki_link, '') - self.assertEqual(book.gazeta_link, '') - self.assertEqual(book._short_html, '') - self.assertEqual(book.description, '') - - tags = [ (tag.category, tag.slug) for tag in book.tags ] - tags.sort() - - self.assertEqual(tags, self.expected_tags) - - def test_not_quite_empty_book(self): - """ Not empty, but without any real text. - - Should work like any other non-empty book. - """ - - BOOK_TEXT = """ - - Nic - - """ - - book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) - self.assertTrue(book.has_html_file()) - - def test_book_with_fragment(self): - BOOK_TEXT = """ - - LoveAla ma kota - - """ - - book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) - self.assertTrue(book.has_html_file()) - - self.assertEqual(book.fragments.count(), 1) - self.assertEqual(book.fragments.all()[0].text, u'

Ala ma kota

\n') - - self.assert_(('theme', 'love') in [ (tag.category, tag.slug) for tag in book.tags ]) - - def test_book_replace_title(self): - BOOK_TEXT = """""" - self.book_info.title = u"Extraordinary" - book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) - - tags = [ (tag.category, tag.slug) for tag in book.tags ] - tags.sort() - - self.assertEqual(tags, self.expected_tags) - - def test_book_replace_author(self): - BOOK_TEXT = """""" - self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen") - book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) - - tags = [ (tag.category, tag.slug) for tag in book.tags ] - tags.sort() - - self.expected_tags.remove(('author', 'jim-lazy')) - self.expected_tags.append(('author', 'hans-christian-andersen')) - self.expected_tags.sort() - - self.assertEqual(tags, self.expected_tags) - - # the old tag should disappear - self.assertRaises(models.Tag.DoesNotExist, models.Tag.objects.get, - slug="jim-lazy", category="author") - - - -class BooksByTagTests(TestCase): - """ tests the /katalog/tag page for found books """ - - def setUp(self): - author = PersonStub(("Common",), "Man") - tags = dict(genre='G', epoch='E', author=author, kind="K") - - # grandchild - kwargs = info_args(u"GChild") - kwargs.update(tags) - gchild_info = BookInfoStub(**kwargs) - # child - kwargs = info_args(u"Child") - kwargs.update(tags) - child_info = BookInfoStub(parts=[gchild_info.url], **kwargs) - # other grandchild - kwargs = info_args(u"Different GChild") - kwargs.update(tags) - diffgchild_info = BookInfoStub(**kwargs) - # other child - kwargs = info_args(u"Different Child") - kwargs.update(tags) - kwargs['kind'] = 'K2' - diffchild_info = BookInfoStub(parts=[diffgchild_info.url], **kwargs) - # parent - kwargs = info_args(u"Parent") - kwargs.update(tags) - parent_info = BookInfoStub(parts=[child_info.url, diffchild_info.url], **kwargs) - - # create the books - book_file = ContentFile('') - for info in gchild_info, child_info, diffgchild_info, diffchild_info, parent_info: - book = models.Book.from_text_and_meta(book_file, info) - - # useful tags - self.author = models.Tag.objects.get(name='Common Man', category='author') - tag_empty = models.Tag(name='Empty tag', slug='empty', category='author') - tag_empty.save() - - self.client = Client() - - - def tearDown(self): - models.Book.objects.all().delete() - - - def test_nonexistent_tag(self): - """ Looking for a non-existent tag should yield 404 """ - self.assertEqual(404, self.client.get('/katalog/czeslaw_milosz/').status_code) - - def test_book_tag(self): - """ Looking for a book tag isn't permitted """ - self.assertEqual(404, self.client.get('/katalog/parent/').status_code) - - def test_tag_empty(self): - """ Tag with no books should return no books """ - context = self.client.get('/katalog/empty/').context - self.assertEqual(0, len(context['object_list'])) - - def test_tag_common(self): - """ Filtering by tag should only yield top-level books. """ - context = self.client.get('/katalog/%s/' % self.author.slug).context - self.assertEqual([book.title for book in context['object_list']], - ['Parent']) - - def test_tag_child(self): - """ Filtering by child's tag should yield the child """ - context = self.client.get('/katalog/k2/').context - self.assertEqual([book.title for book in context['object_list']], - ['Different Child']) - - def test_tag_child_jump(self): - """ Of parent and grandchild, only parent should be returned. """ - context = self.client.get('/katalog/k/').context - self.assertEqual([book.title for book in context['object_list']], - ['Parent']) - - -class TagRelatedTagsTests(TestCase): - """ tests the /katalog/tag/ page for related tags """ - - def setUp(self): - author = PersonStub(("Common",), "Man") - - gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind", - **info_args(u"GChild")) - child1_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind", - parts=[gchild_info.url], - **info_args(u"Child1")) - child2_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind", - **info_args(u"Child2")) - parent_info = BookInfoStub(author=author, genre="Genre", epoch='Epoch', kind="Kind", - parts=[child1_info.url, child2_info.url], - **info_args(u"Parent")) - - for info in gchild_info, child1_info, child2_info, parent_info: - book_text = """ - - Theme, %sTheme - Ala ma kota - - - """ % info.title.encode('utf-8') - book = models.Book.from_text_and_meta(ContentFile(book_text), info) - book.save() - - tag_empty = models.Tag(name='Empty tag', slug='empty', category='author') - tag_empty.save() - - self.client = Client() - - - def tearDown(self): - models.Book.objects.all().delete() - - - def test_empty(self): - """ empty tag should have no related tags """ - - cats = self.client.get('/katalog/empty/').context['categories'] - self.assertEqual(cats, {}, 'tags related to empty tag') - - - def test_has_related(self): - """ related own and descendants' tags should be generated """ - - cats = self.client.get('/katalog/kind/').context['categories'] - self.assertTrue('Common Man' in [tag.name for tag in cats['author']], - 'missing `author` related tag') - self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']], - 'missing `epoch` related tag') - self.assertTrue("ChildKind" in [tag.name for tag in cats['kind']], - "missing `kind` related tag") - self.assertTrue("Genre" in [tag.name for tag in cats['genre']], - 'missing `genre` related tag') - self.assertTrue("ChildGenre" in [tag.name for tag in cats['genre']], - "missing child's related tag") - self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']], - "missing grandchild's related tag") - self.assertTrue('Theme' in [tag.name for tag in cats['theme']], - "missing related theme") - self.assertTrue('Child1Theme' in [tag.name for tag in cats['theme']], - "missing child's related theme") - self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']], - "missing grandchild's related theme") - - - def test_related_differ(self): - """ related tags shouldn't include filtering tags """ - - cats = self.client.get('/katalog/kind/').context['categories'] - self.assertFalse('Kind' in [tag.name for tag in cats['kind']], - 'filtering tag wrongly included in related') - cats = self.client.get('/katalog/theme/').context['categories'] - self.assertFalse('Theme' in [tag.name for tag in cats['theme']], - 'filtering theme wrongly included in related') - - - def test_parent_tag_once(self): - """ if parent and descendants have a common tag, count it only once """ - - cats = self.client.get('/katalog/kind/').context['categories'] - self.assertEqual([(tag.name, tag.count) for tag in cats['epoch']], - [('Epoch', 1)], - 'wrong related tag epoch tag on tag page') - - - def test_siblings_tags_add(self): - """ if children have tags and parent hasn't, count the children """ - - cats = self.client.get('/katalog/epoch/').context['categories'] - self.assertTrue(('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']], - 'wrong related kind tags on tag page') - - def test_themes_add(self): - """ all occurencies of theme should be counted """ - - cats = self.client.get('/katalog/epoch/').context['categories'] - self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']], - 'wrong related theme count') - - - -class CleanTagRelationTests(TestCase): - """ tests for tag relations cleaning after deleting things """ - - def setUp(self): - author = PersonStub(("Common",), "Man") - - book_info = BookInfoStub(author=author, genre="G", epoch='E', kind="K", - **info_args(u"Book")) - book_text = """ - ThemeAla ma kota - - - """ - book = models.Book.from_text_and_meta(ContentFile(book_text), book_info) - book.save() - - self.client = Client() - - - def tearDown(self): - models.Book.objects.all().delete() - - - def test_delete_objects(self): - """ there should be no related tags left after deleting some objects """ - - models.Book.objects.all().delete() - cats = self.client.get('/katalog/k/').context['categories'] - self.assertEqual({}, cats) - - - def test_deleted_tag(self): - """ there should be no tag relations left after deleting tags """ - - models.Tag.objects.all().delete() - cats = self.client.get('/katalog/lektura/book/').context['categories'] - self.assertEqual(cats, {}) - - -class TestIdenticalTag(TestCase): - - def setUp(self): - author = PersonStub(("A",), "B") - - book_info = BookInfoStub(author=author, genre="A B", epoch='A B', kind="A B", - **info_args(u"A B")) - book_text = """ - A BAla ma kota - - - """ - book = models.Book.from_text_and_meta(ContentFile(book_text), book_info) - book.save() - - self.client = Client() - - - def tearDown(self): - models.Book.objects.all().delete() - - - def test_book_tags(self): - """ there should be all related tags in relevant categories """ - - cats = self.client.get('/katalog/lektura/a-b/').context['categories'] - for category in 'author', 'kind', 'genre', 'epoch', 'theme': - self.assertTrue('A B' in [tag.name for tag in cats[category]], - 'missing related tag for %s' % category) - - def test_qualified_url(self): - categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind':'rodzaj', 'genre':'gatunek'} - for cat, localcat in categories.iteritems(): - context = self.client.get('/katalog/%s/a-b/' % localcat).context - self.assertEqual(1, len(context['object_list'])) - self.assertNotEqual({}, context['categories']) - self.assertFalse(cat in context['categories']) - diff --git a/apps/catalogue/tests/__init__.py b/apps/catalogue/tests/__init__.py new file mode 100644 index 000000000..d656d455b --- /dev/null +++ b/apps/catalogue/tests/__init__.py @@ -0,0 +1,3 @@ +from catalogue.tests.book_import import * +from catalogue.tests.tags import * +from catalogue.tests.search import * diff --git a/apps/catalogue/tests/book_import.py b/apps/catalogue/tests/book_import.py new file mode 100644 index 000000000..fed99226f --- /dev/null +++ b/apps/catalogue/tests/book_import.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +from django.core.files.base import ContentFile +from catalogue.test_utils import * +from catalogue import models + +class BookImportLogicTests(WLTestCase): + + def setUp(self): + WLTestCase.setUp(self) + self.book_info = BookInfoStub( + url=u"http://wolnelektury.pl/example/default_book", + about=u"http://wolnelektury.pl/example/URI/default_book", + title=u"Default Book", + author=PersonStub(("Jim",), "Lazy"), + kind="X-Kind", + genre="X-Genre", + epoch="X-Epoch", + ) + + self.expected_tags = [ + ('author', 'jim-lazy'), + ('genre', 'x-genre'), + ('epoch', 'x-epoch'), + ('kind', 'x-kind'), + ] + self.expected_tags.sort() + + def test_empty_book(self): + BOOK_TEXT = "" + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + + self.assertEqual(book.title, "Default Book") + self.assertEqual(book.slug, "default_book") + self.assert_(book.parent is None) + self.assertFalse(book.has_html_file()) + + # no fragments generated + self.assertEqual(book.fragments.count(), 0) + + # TODO: this should be filled out probably... + self.assertEqual(book.wiki_link, '') + self.assertEqual(book.gazeta_link, '') + self.assertEqual(book._short_html, '') + self.assertEqual(book.description, '') + + tags = [ (tag.category, tag.slug) for tag in book.tags ] + tags.sort() + + self.assertEqual(tags, self.expected_tags) + + def test_not_quite_empty_book(self): + """ Not empty, but without any real text. + + Should work like any other non-empty book. + """ + + BOOK_TEXT = """ + + Nic + + """ + + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + self.assertTrue(book.has_html_file()) + + def test_book_with_fragment(self): + BOOK_TEXT = """ + + LoveAla ma kota + + """ + + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + self.assertTrue(book.has_html_file()) + + self.assertEqual(book.fragments.count(), 1) + self.assertEqual(book.fragments.all()[0].text, u'

Ala ma kota

\n') + + self.assert_(('theme', 'love') in [ (tag.category, tag.slug) for tag in book.tags ]) + + def test_book_replace_title(self): + BOOK_TEXT = """""" + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + self.book_info.title = u"Extraordinary" + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + + tags = [ (tag.category, tag.slug) for tag in book.tags ] + tags.sort() + + self.assertEqual(tags, self.expected_tags) + + def test_book_replace_author(self): + BOOK_TEXT = """""" + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen") + book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) + + tags = [ (tag.category, tag.slug) for tag in book.tags ] + tags.sort() + + self.expected_tags.remove(('author', 'jim-lazy')) + self.expected_tags.append(('author', 'hans-christian-andersen')) + self.expected_tags.sort() + + self.assertEqual(tags, self.expected_tags) + + # the old tag should disappear + self.assertRaises(models.Tag.DoesNotExist, models.Tag.objects.get, + slug="jim-lazy", category="author") diff --git a/apps/catalogue/tests/search.py b/apps/catalogue/tests/search.py new file mode 100644 index 000000000..93bec870e --- /dev/null +++ b/apps/catalogue/tests/search.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from catalogue import models, views +from catalogue.test_utils import * + +from nose.tools import raises + +class BasicSearchLogicTests(WLTestCase): + + def setUp(self): + WLTestCase.setUp(self) + self.author_tag = models.Tag.objects.create( + name=u'Adam Mickiewicz [SubWord]', + category=u'author', slug="one") + + self.unicode_tag = models.Tag.objects.create( + name=u'Tadeusz Żeleński (Boy)', + category=u'author', slug="two") + + self.polish_tag = models.Tag.objects.create( + name=u'ĘÓĄŚŁŻŹĆŃęóąśłżźćń', + category=u'author', slug="three") + + @raises(ValueError) + def test_empty_query(self): + """ Check that empty queries raise an error. """ + views.find_best_matches(u'') + + @raises(ValueError) + def test_one_letter_query(self): + """ Check that one letter queries aren't permitted. """ + views.find_best_matches(u't') + + def test_match_by_prefix(self): + """ Tags should be matched by prefix of words within it's name. """ + self.assertEqual(views.find_best_matches(u'Ada'), (self.author_tag,)) + self.assertEqual(views.find_best_matches(u'Mic'), (self.author_tag,)) + self.assertEqual(views.find_best_matches(u'Mickiewicz'), (self.author_tag,)) + + def test_match_case_insensitive(self): + """ Tag names should match case insensitive. """ + self.assertEqual(views.find_best_matches(u'adam mickiewicz'), (self.author_tag,)) + + def test_match_case_insensitive_unicode(self): + """ Tag names should match case insensitive (unicode). """ + self.assertEqual(views.find_best_matches(u'tadeusz żeleński (boy)'), (self.unicode_tag,)) + + def test_word_boundary(self): + self.assertEqual(views.find_best_matches(u'SubWord'), (self.author_tag,)) + self.assertEqual(views.find_best_matches(u'[SubWord'), (self.author_tag,)) + + def test_unrelated_search(self): + self.assertEqual(views.find_best_matches(u'alamakota'), tuple()) + self.assertEqual(views.find_best_matches(u'Adama'), ()) + + def test_infix_doesnt_match(self): + """ Searching for middle of a word shouldn't match. """ + self.assertEqual(views.find_best_matches(u'deusz'), tuple()) + + def test_diactricts_removal_pl(self): + """ Tags should match both with and without national characters. """ + self.assertEqual(views.find_best_matches(u'ĘÓĄŚŁŻŹĆŃęóąśłżźćń'), (self.polish_tag,)) + self.assertEqual(views.find_best_matches(u'EOASLZZCNeoaslzzcn'), (self.polish_tag,)) + self.assertEqual(views.find_best_matches(u'eoaslzzcneoaslzzcn'), (self.polish_tag,)) + + def test_diactricts_query_removal_pl(self): + """ Tags without national characters shouldn't be matched by queries with them. """ + self.assertEqual(views.find_best_matches(u'Adąm'), ()) + + def test_sloppy(self): + self.assertEqual(views.find_best_matches(u'Żelenski'), (self.unicode_tag,)) + self.assertEqual(views.find_best_matches(u'zelenski'), (self.unicode_tag,)) diff --git a/apps/catalogue/tests/tags.py b/apps/catalogue/tests/tags.py new file mode 100644 index 000000000..f9102a7c8 --- /dev/null +++ b/apps/catalogue/tests/tags.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +from catalogue import models +from catalogue.test_utils import * +from django.core.files.base import ContentFile +from slughifi import slughifi + +from nose.tools import raises + +def info_args(title): + """ generate some keywords for comfortable BookInfoCreation """ + slug = unicode(slughifi(title)) + return { + 'title': unicode(title), + 'slug': slug, + 'url': u"http://wolnelektury.pl/example/%s" % slug, + 'about': u"http://wolnelektury.pl/example/URI/%s" % slug, + } + + +class BooksByTagTests(WLTestCase): + """ tests the /katalog/tag page for found books """ + + def setUp(self): + WLTestCase.setUp(self) + author = PersonStub(("Common",), "Man") + tags = dict(genre='G', epoch='E', author=author, kind="K") + + # grandchild + kwargs = info_args(u"GChild") + kwargs.update(tags) + gchild_info = BookInfoStub(**kwargs) + # child + kwargs = info_args(u"Child") + kwargs.update(tags) + child_info = BookInfoStub(parts=[gchild_info.url], **kwargs) + # other grandchild + kwargs = info_args(u"Different GChild") + kwargs.update(tags) + diffgchild_info = BookInfoStub(**kwargs) + # other child + kwargs = info_args(u"Different Child") + kwargs.update(tags) + kwargs['kind'] = 'K2' + diffchild_info = BookInfoStub(parts=[diffgchild_info.url], **kwargs) + # parent + kwargs = info_args(u"Parent") + kwargs.update(tags) + parent_info = BookInfoStub(parts=[child_info.url, diffchild_info.url], **kwargs) + + # create the books + book_file = ContentFile('') + for info in gchild_info, child_info, diffgchild_info, diffchild_info, parent_info: + book = models.Book.from_text_and_meta(book_file, info) + + # useful tags + self.author = models.Tag.objects.get(name='Common Man', category='author') + models.Tag.objects.create(name='Empty tag', slug='empty', category='author') + + def test_nonexistent_tag(self): + """ Looking for a non-existent tag should yield 404 """ + # NOTE: this yields a false positive, 'cause of URL change + self.assertEqual(404, self.client.get('/katalog/czeslaw_milosz/').status_code) + + def test_book_tag(self): + """ Looking for a book tag isn't permitted """ + self.assertEqual(404, self.client.get('/katalog/parent/').status_code) + + def test_tag_empty(self): + """ Tag with no books should return no books """ + context = self.client.get('/katalog/empty/').context + self.assertEqual(0, len(context['object_list'])) + + def test_tag_common(self): + """ Filtering by tag should only yield top-level books. """ + context = self.client.get('/katalog/%s/' % self.author.slug).context + self.assertEqual([book.title for book in context['object_list']], + ['Parent']) + + def test_tag_child(self): + """ Filtering by child's tag should yield the child """ + context = self.client.get('/katalog/k2/').context + self.assertEqual([book.title for book in context['object_list']], + ['Different Child']) + + def test_tag_child_jump(self): + """ Of parent and grandchild, only parent should be returned. """ + context = self.client.get('/katalog/k/').context + self.assertEqual([book.title for book in context['object_list']], + ['Parent']) + + +class TagRelatedTagsTests(WLTestCase): + """ tests the /katalog/tag/ page for related tags """ + + def setUp(self): + WLTestCase.setUp(self) + author = PersonStub(("Common",), "Man") + + gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind", + **info_args(u"GChild")) + child1_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind", + parts=[gchild_info.url], + **info_args(u"Child1")) + child2_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind", + **info_args(u"Child2")) + parent_info = BookInfoStub(author=author, genre="Genre", epoch='Epoch', kind="Kind", + parts=[child1_info.url, child2_info.url], + **info_args(u"Parent")) + + for info in gchild_info, child1_info, child2_info, parent_info: + book_text = """ + + Theme, %sTheme + Ala ma kota + + + """ % info.title.encode('utf-8') + book = models.Book.from_text_and_meta(ContentFile(book_text), info) + book.save() + + tag_empty = models.Tag(name='Empty tag', slug='empty', category='author') + tag_empty.save() + + def test_empty(self): + """ empty tag should have no related tags """ + + cats = self.client.get('/katalog/empty/').context['categories'] + self.assertEqual(cats, {}, 'tags related to empty tag') + + def test_has_related(self): + """ related own and descendants' tags should be generated """ + + cats = self.client.get('/katalog/kind/').context['categories'] + self.assertTrue('Common Man' in [tag.name for tag in cats['author']], + 'missing `author` related tag') + self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']], + 'missing `epoch` related tag') + self.assertTrue("ChildKind" in [tag.name for tag in cats['kind']], + "missing `kind` related tag") + self.assertTrue("Genre" in [tag.name for tag in cats['genre']], + 'missing `genre` related tag') + self.assertTrue("ChildGenre" in [tag.name for tag in cats['genre']], + "missing child's related tag") + self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']], + "missing grandchild's related tag") + self.assertTrue('Theme' in [tag.name for tag in cats['theme']], + "missing related theme") + self.assertTrue('Child1Theme' in [tag.name for tag in cats['theme']], + "missing child's related theme") + self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']], + "missing grandchild's related theme") + + + def test_related_differ(self): + """ related tags shouldn't include filtering tags """ + + cats = self.client.get('/katalog/kind/').context['categories'] + self.assertFalse('Kind' in [tag.name for tag in cats['kind']], + 'filtering tag wrongly included in related') + cats = self.client.get('/katalog/theme/').context['categories'] + self.assertFalse('Theme' in [tag.name for tag in cats['theme']], + 'filtering theme wrongly included in related') + + + def test_parent_tag_once(self): + """ if parent and descendants have a common tag, count it only once """ + + cats = self.client.get('/katalog/kind/').context['categories'] + self.assertEqual([(tag.name, tag.count) for tag in cats['epoch']], + [('Epoch', 1)], + 'wrong related tag epoch tag on tag page') + + + def test_siblings_tags_add(self): + """ if children have tags and parent hasn't, count the children """ + + cats = self.client.get('/katalog/epoch/').context['categories'] + self.assertTrue(('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']], + 'wrong related kind tags on tag page') + + def test_themes_add(self): + """ all occurencies of theme should be counted """ + + cats = self.client.get('/katalog/epoch/').context['categories'] + self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']], + 'wrong related theme count') + + +class CleanTagRelationTests(WLTestCase): + """ tests for tag relations cleaning after deleting things """ + + def setUp(self): + WLTestCase.setUp(self) + author = PersonStub(("Common",), "Man") + + book_info = BookInfoStub(author=author, genre="G", epoch='E', kind="K", + **info_args(u"Book")) + book_text = """ + ThemeAla ma kota + + + """ + book = models.Book.from_text_and_meta(ContentFile(book_text), book_info) + + def test_delete_objects(self): + """ there should be no related tags left after deleting some objects """ + + models.Book.objects.all().delete() + cats = self.client.get('/katalog/k/').context['categories'] + self.assertEqual(cats, {}) + + def test_deleted_tag(self): + """ there should be no tag relations left after deleting tags """ + + models.Tag.objects.all().delete() + cats = self.client.get('/katalog/lektura/book/').context['categories'] + self.assertEqual(cats, {}) + + +class TestIdenticalTag(WLTestCase): + + def setUp(self): + WLTestCase.setUp(self) + author = PersonStub((), "Tag") + + self.book_info = BookInfoStub(author=author, + genre="tag", + epoch='tag', + kind="tag", + **info_args(u"tag")) + self.book_text = """ + + + tagAla ma kota + + + + """ + + + def test_book_tags(self): + """ there should be all related tags in relevant categories """ + models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info) + + cats = self.client.get('/katalog/lektura/tag/').context['categories'] + for category in 'author', 'kind', 'genre', 'epoch', 'theme': + self.assertTrue('tag' in [tag.name for tag in cats[category]], + 'missing related tag for %s' % category) + + def test_qualified_url(self): + models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info) + categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind':'rodzaj', 'genre':'gatunek'} + for cat, localcat in categories.iteritems(): + context = self.client.get('/katalog/%s/tag/' % localcat).context + self.assertEqual(1, len(context['object_list'])) + self.assertNotEqual({}, context['categories']) + self.assertFalse(cat in context['categories']) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index 25389f48b..4d44dc63a 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -20,7 +20,7 @@ urlpatterns = patterns('catalogue.views', # tools url(r'^zegar', 'clock', name='clock'), - + # Public interface. Do not change this URLs. url(r'^lektura/(?P[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'), url(r'^lektura/(?P[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'), diff --git a/apps/catalogue/utils.py b/apps/catalogue/utils.py index 36bd9e700..e44b3792f 100644 --- a/apps/catalogue/utils.py +++ b/apps/catalogue/utils.py @@ -19,7 +19,7 @@ MAX_SESSION_KEY = 18446744073709551616L # 2 << 63 def get_random_hash(seed): - sha_digest = sha_constructor('%s%s%s%s' % + sha_digest = sha_constructor('%s%s%s%s' % (randrange(0, MAX_SESSION_KEY), time.time(), unicode(seed).encode('utf-8', 'replace'), settings.SECRET_KEY)).digest() return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower() diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 4476fd266..47abe637d 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -9,7 +9,7 @@ import pprint import traceback import re import itertools -from operator import itemgetter +from operator import itemgetter from django.conf import settings from django.template import RequestContext @@ -83,7 +83,7 @@ def differentiate_tags(request, tags, ambiguous_slugs): 'tags': [tag] }) return render_to_response('catalogue/differentiate_tags.html', - {'tags': tags, 'options': options, 'unparsed': ambiguous_slugs[1:]}, + {'tags': tags, 'options': options, 'unparsed': ambiguous_slugs[1:]}, context_instance=RequestContext(request)) @@ -122,7 +122,7 @@ def tagged_object_list(request, tags=''): fragments = models.Fragment.tagged.with_any(l_tags, fragments) # newtagging goes crazy if we just try: - #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True, + #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True, # extra={'where': ["catalogue_tag.category != 'book'"]}) fragment_keys = [fragment.pk for fragment in fragments] if fragment_keys: @@ -141,7 +141,7 @@ def tagged_object_list(request, tags=''): descendants_keys = [book.pk for book in models.Book.tagged.with_any(l_tags)] if descendants_keys: objects = objects.exclude(pk__in=descendants_keys) - + # get related tags from `tag_counter` and `theme_counter` related_counts = {} tags_pks = [tag.pk for tag in tags] @@ -154,7 +154,7 @@ def tagged_object_list(request, tags=''): related_tags = [tag for tag in related_tags if tag not in tags] for tag in related_tags: tag.count = related_counts[tag.pk] - + categories = split_tags(related_tags) del related_tags @@ -238,7 +238,7 @@ def book_text(request, slug): def _no_diacritics_regexp(query): """ returns a regexp for searching for a query without diacritics - + should be locale-aware """ names = { u'a':u'aąĄ', u'c':u'cćĆ', u'e':u'eęĘ', u'l': u'lłŁ', u'n':u'nńŃ', u'o':u'oóÓ', u's':u'sśŚ', u'z':u'zźżŹŻ', @@ -256,16 +256,16 @@ def unicode_re_escape(query): def _word_starts_with(name, prefix): """returns a Q object getting models having `name` contain a word starting with `prefix` - + We define word characters as alphanumeric and underscore, like in JS. - + Works for MySQL, PostgreSQL, Oracle. For SQLite, _sqlite* version is substituted for this. """ kwargs = {} prefix = _no_diacritics_regexp(unicode_re_escape(prefix)) - # can't use [[:<:]] (word start), + # can't use [[:<:]] (word start), # but we want both `xy` and `(xy` to catch `(xyz)` kwargs['%s__iregex' % name] = u"(^|[^[:alnum:]_])%s" % prefix @@ -273,8 +273,8 @@ def _word_starts_with(name, prefix): def _sqlite_word_starts_with(name, prefix): - """ version of _word_starts_with for SQLite - + """ version of _word_starts_with for SQLite + SQLite in Django uses Python re module """ kwargs = {} @@ -320,12 +320,12 @@ def _get_result_type(match): def find_best_matches(query, user=None): """ Finds a Book, Tag or Bookstub best matching a query. - + Returns a with: - zero elements when nothing is found, - one element when a best result is found, - more then one element on multiple exact matches - + Raises a ValueError on too short a query. """ @@ -458,7 +458,7 @@ 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. + be used for large dynamic PDF files. """ shelf = get_object_or_404(models.Tag, slug=slug, category='set') diff --git a/apps/chunks/models.py b/apps/chunks/models.py index 86f04660c..cd9cf4e32 100644 --- a/apps/chunks/models.py +++ b/apps/chunks/models.py @@ -15,7 +15,7 @@ class Chunk(models.Model): ordering = ('key',) verbose_name = _('chunk') verbose_name_plural = _('chunks') - + def __unicode__(self): return self.key @@ -23,7 +23,7 @@ class Chunk(models.Model): class Attachment(models.Model): key = models.CharField(_('key'), help_text=_('A unique name for this attachment'), primary_key=True, max_length=255) attachment = models.FileField(upload_to='chunks/attachment') - + class Meta: ordering = ('key',) verbose_name, verbose_name_plural = _('attachment'), _('attachments') diff --git a/apps/chunks/templatetags/chunks.py b/apps/chunks/templatetags/chunks.py index 595482f41..083c48aa5 100644 --- a/apps/chunks/templatetags/chunks.py +++ b/apps/chunks/templatetags/chunks.py @@ -30,7 +30,7 @@ class ChunkNode(template.Node): def __init__(self, key, cache_time=0): self.key = key self.cache_time = cache_time - + def render(self, context): try: cache_key = 'chunk_' + self.key @@ -44,7 +44,7 @@ class ChunkNode(template.Node): n.save() return '' return content - + register.tag('chunk', do_get_chunk) @@ -58,6 +58,6 @@ def attachment(key, cache_time=0): return c.attachment.url except Attachment.DoesNotExist: return '' - + register.simple_tag(attachment) diff --git a/apps/compress/filter_base.py b/apps/compress/filter_base.py index 9b98531b6..9bb23b2ff 100644 --- a/apps/compress/filter_base.py +++ b/apps/compress/filter_base.py @@ -6,7 +6,7 @@ class FilterBase: raise NotImplementedError def filter_js(self, js): raise NotImplementedError - + class FilterError(Exception): """ This exception is raised when a filter fails diff --git a/apps/compress/filters/csstidy/__init__.py b/apps/compress/filters/csstidy/__init__.py index d40e8eebb..ae7c10313 100644 --- a/apps/compress/filters/csstidy/__init__.py +++ b/apps/compress/filters/csstidy/__init__.py @@ -18,16 +18,16 @@ class CSSTidyFilter(FilterBase): tmp_file.flush() output_file = tempfile.NamedTemporaryFile(mode='w+b') - + command = '%s %s %s %s' % (BINARY, tmp_file.name, ARGUMENTS, output_file.name) - + command_output = os.popen(command).read() - + filtered_css = output_file.read() output_file.close() tmp_file.close() - + if self.verbose: print command_output - + return filtered_css diff --git a/apps/compress/filters/csstidy_python/__init__.py b/apps/compress/filters/csstidy_python/__init__.py index 7d581ed43..03a4ac012 100644 --- a/apps/compress/filters/csstidy_python/__init__.py +++ b/apps/compress/filters/csstidy_python/__init__.py @@ -8,12 +8,12 @@ COMPRESS_CSSTIDY_SETTINGS = getattr(settings, 'COMPRESS_CSSTIDY_SETTINGS', {}) class CSSTidyFilter(FilterBase): def filter_css(self, css): tidy = CSSTidy() - + for k, v in COMPRESS_CSSTIDY_SETTINGS.items(): tidy.setSetting(k, v) tidy.parse(css) r = tidy.Output('string') - + return r diff --git a/apps/compress/management/commands/synccompress.py b/apps/compress/management/commands/synccompress.py index 6e31d254d..f5624f59d 100644 --- a/apps/compress/management/commands/synccompress.py +++ b/apps/compress/management/commands/synccompress.py @@ -14,7 +14,7 @@ class Command(NoArgsCommand): args = '' def handle_noargs(self, **options): - + force = options.get('force', False) verbosity = int(options.get('verbosity', 1)) diff --git a/apps/compress/utils.py b/apps/compress/utils.py index 3c59728b9..89b04a56e 100644 --- a/apps/compress/utils.py +++ b/apps/compress/utils.py @@ -97,7 +97,7 @@ def get_version(version): except ValueError: return str(version) -def remove_files(path, filename, verbosity=0): +def remove_files(path, filename, verbosity=0): regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'\d+')))) for f in os.listdir(path): diff --git a/apps/infopages/migrations/0001_initial.py b/apps/infopages/migrations/0001_initial.py index a1289a2c6..a28ce3aa2 100644 --- a/apps/infopages/migrations/0001_initial.py +++ b/apps/infopages/migrations/0001_initial.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'InfoPage' db.create_table('infopages_infopage', ( ('title_de', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True)), @@ -55,14 +55,14 @@ class Migration(SchemaMigration): call_command("loaddata", "wl_data") db.send_create_signal('infopages', ['InfoPage']) - - + + def backwards(self, orm): - + # Deleting model 'InfoPage' db.delete_table('infopages_infopage') - - + + models = { 'infopages.infopage': { 'Meta': {'object_name': 'InfoPage'}, @@ -106,5 +106,5 @@ class Migration(SchemaMigration): 'title_uk': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}) } } - + complete_apps = ['infopages'] diff --git a/apps/infopages/models.py b/apps/infopages/models.py index 5026da23e..9fe7b3287 100644 --- a/apps/infopages/models.py +++ b/apps/infopages/models.py @@ -9,7 +9,7 @@ class InfoPage(models.Model): """ An InfoPage is used to display a two-column flatpage """ - + page_title = models.CharField(_('page title'), max_length=120, blank=True) slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True) title = models.CharField(_('title'), max_length=120, blank=True) @@ -20,7 +20,7 @@ class InfoPage(models.Model): ordering = ('slug',) verbose_name = _('info page') verbose_name_plural = _('info pages') - + def __unicode__(self): return self.title diff --git a/apps/lessons/models.py b/apps/lessons/models.py index dc113ed16..ffb2b6c96 100644 --- a/apps/lessons/models.py +++ b/apps/lessons/models.py @@ -16,14 +16,14 @@ class Document(models.Model): slideshare_id = models.CharField(_('slideshare ID'), blank=True, max_length=120) description = models.TextField(_('description'), blank=True) created_at = models.DateTimeField(auto_now_add=True) - + def slideshare_player(self): base, ext = path.splitext(self.file.name) if ext in ('.ppt', '.pps', '.pot', '.pptx', '.potx', '.ppsx', '.odp', '.key', '.zip', '.pdf',): return 'ssplayer2.swf' else: return 'ssplayerd.swf' - + class Meta: ordering = ['slug'] verbose_name, verbose_name_plural = _("document"), _("documents") diff --git a/apps/lessons/urls.py b/apps/lessons/urls.py index 69a47e3ea..6c89e5f6b 100644 --- a/apps/lessons/urls.py +++ b/apps/lessons/urls.py @@ -15,7 +15,7 @@ urlpatterns = patterns('', 'form': forms.SearchForm(), }, }, name='lessons_document_list'), - + url(r'^(?P[a-zA-Z0-9_-]+)/$', 'lessons.views.document_detail', name='lessons_document_detail'), ) diff --git a/apps/lessons/views.py b/apps/lessons/views.py index 69380d688..242526d86 100644 --- a/apps/lessons/views.py +++ b/apps/lessons/views.py @@ -11,7 +11,7 @@ def document_detail(request, slug): template_name = 'lessons/document_detail.html' if request.is_ajax(): template_name = 'lessons/ajax_document_detail.html' - + return object_detail(request, slug=slug, slug_field='slug', diff --git a/apps/newtagging/admin.py b/apps/newtagging/admin.py index 956d2cf9b..a8f511058 100644 --- a/apps/newtagging/admin.py +++ b/apps/newtagging/admin.py @@ -17,7 +17,7 @@ class FilteredSelectMultiple(forms.SelectMultiple): js = ['js/SelectBox.js' , 'js/SelectFilter2.js'] return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) media = property(_media) - + def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): self.verbose_name = verbose_name self.is_stacked = is_stacked @@ -44,7 +44,7 @@ class TaggableModelForm(forms.ModelForm): kwargs['initial']['tags'] = [tag.id for tag in self.tag_model.objects.get_for_object(kwargs['instance'])] super(TaggableModelForm, self).__init__(*args, **kwargs) self.fields['tags'].choices = [(tag.id, tag.name) for tag in self.tag_model.objects.all()] - + def save(self, commit): obj = super(TaggableModelForm, self).save() tag_ids = self.cleaned_data['tags'] @@ -59,7 +59,7 @@ class TaggableModelForm(forms.ModelForm): class TaggableModelAdmin(admin.ModelAdmin): form = TaggableModelForm - + def get_form(self, request, obj=None): form = super(TaggableModelAdmin, self).get_form(request, obj) form.tag_model = self.tag_model diff --git a/apps/newtagging/managers.py b/apps/newtagging/managers.py index 7870092e0..3107070ea 100644 --- a/apps/newtagging/managers.py +++ b/apps/newtagging/managers.py @@ -14,15 +14,15 @@ class ModelTagManager(models.Manager): def __init__(self, tag_model): super(ModelTagManager, self).__init__() self.tag_model = tag_model - + def get_query_set(self): content_type = ContentType.objects.get_for_model(self.model) return self.tag_model.objects.filter( items__content_type__pk=content_type.pk).distinct() - + def related(self, tags, *args, **kwargs): return self.tag_model.objects.related_for_model(tags, self.model, *args, **kwargs) - + def usage(self, *args, **kwargs): return self.tag_model.objects.usage_for_model(self.model, *args, **kwargs) @@ -62,7 +62,7 @@ class TagDescriptor(object): """ def __init__(self, tag_model): self.tag_model = tag_model - + def __get__(self, instance, owner): if not instance: tag_manager = ModelTagManager(self.tag_model) diff --git a/apps/newtagging/models.py b/apps/newtagging/models.py index 2055ec318..1c35254bb 100644 --- a/apps/newtagging/models.py +++ b/apps/newtagging/models.py @@ -6,7 +6,7 @@ Models and managers for generic tagging. # Python 2.3 compatibility try: set -except NameError: +except NameError: from sets import Set as set from django.contrib.contenttypes import generic @@ -45,7 +45,7 @@ class TagManager(models.Manager): def __init__(self, intermediary_table_model): super(TagManager, self).__init__() self.intermediary_table_model = intermediary_table_model - + def update_tags(self, obj, tags): """ Update tags associated with an object. @@ -54,7 +54,7 @@ class TagManager(models.Manager): current_tags = list(self.filter(items__content_type__pk=content_type.pk, items__object_id=obj.pk)) updated_tags = self.model.get_tag_list(tags) - + # Remove tags which no longer apply tags_for_removal = [tag for tag in current_tags \ if tag not in updated_tags] @@ -66,7 +66,7 @@ class TagManager(models.Manager): for tag in updated_tags: if tag not in current_tags: self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj) - + def remove_tag(self, obj, tag): """ Remove tag from an object. @@ -83,7 +83,7 @@ class TagManager(models.Manager): ctype = ContentType.objects.get_for_model(obj) return self.filter(items__content_type__pk=ctype.pk, items__object_id=obj.pk) - + def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None, extra=None): """ Perform the custom SQL query for ``usage_for_model`` and @@ -94,12 +94,12 @@ class TagManager(models.Manager): model_table = qn(model._meta.db_table) model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column)) tag_columns = self._get_tag_columns() - + if extra is None: extra = {} extra_where = '' if 'where' in extra: extra_where = 'AND ' + ' AND '.join(extra['where']) - + query = """ SELECT DISTINCT %(tag_columns)s%(count_sql)s FROM @@ -224,12 +224,12 @@ class TagManager(models.Manager): tag_count = len(tags) tagged_item_table = qn(self.intermediary_table_model._meta.db_table) tag_columns = self._get_tag_columns() - + if extra is None: extra = {} extra_where = '' if 'where' in extra: extra_where = 'AND ' + ' AND '.join(extra['where']) - + # Temporary table in this query is a hack to prevent MySQL from executing # inner query as dependant query (which could result in severe performance loss) query = """ @@ -301,7 +301,7 @@ class TaggedItemManager(models.Manager): def __init__(self, tag_model): super(TaggedItemManager, self).__init__() self.tag_model = tag_model - + def get_by_model(self, queryset_or_model, tags): """ Create a ``QuerySet`` containing instances of the specified @@ -473,7 +473,7 @@ class TaggedItemManager(models.Manager): def create_intermediary_table_model(model): """Create an intermediary table model for the specific tag model""" name = model.__name__ + 'Relation' - + class Meta: db_table = '%s_relation' % model._meta.db_table unique_together = (('tag', 'content_type', 'object_id'),) @@ -483,8 +483,8 @@ def create_intermediary_table_model(model): return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag) except ObjectDoesNotExist: return u' [%s]' % self.tag - - # Set up a dictionary to simulate declarations within a class + + # Set up a dictionary to simulate declarations within a class attrs = { '__module__': model.__module__, 'Meta': Meta, @@ -513,15 +513,15 @@ class TagMeta(ModelBase): class TagBase(models.Model): """Abstract class to be inherited by model classes.""" __metaclass__ = TagMeta - + class Meta: abstract = True - + @staticmethod def get_tag_list(tag_list): """ Utility function for accepting tag input in a flexible manner. - + You should probably override this method in your subclass. """ if isinstance(tag_list, TagBase): diff --git a/apps/piston/authentication.py b/apps/piston/authentication.py index 7d09707cb..19b8e829d 100644 --- a/apps/piston/authentication.py +++ b/apps/piston/authentication.py @@ -26,7 +26,7 @@ class NoAuthentication(object): class HttpBasicAuthentication(object): """ Basic HTTP authenticater. Synopsis: - + Authentication handlers must implement two methods: - `is_authenticated`: Will be called when checking for authentication. Receives a `request` object, please @@ -46,7 +46,7 @@ class HttpBasicAuthentication(object): if not auth_string: return False - + try: (authmeth, auth) = auth_string.split(" ", 1) @@ -57,12 +57,12 @@ class HttpBasicAuthentication(object): (username, password) = auth.split(':', 1) except (ValueError, binascii.Error): return False - + request.user = self.auth_func(username=username, password=password) \ or AnonymousUser() - + return not request.user in (False, None, AnonymousUser()) - + def challenge(self): resp = HttpResponse("Authorization Required") resp['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm @@ -78,7 +78,7 @@ class HttpBasicSimple(HttpBasicAuthentication): self.password = password super(HttpBasicSimple, self).__init__(auth_func=self.hash, realm=realm) - + def hash(self, username, password): if username == self.user.username and password == self.password: return self.user @@ -122,17 +122,17 @@ def initialize_server_request(request): request.META['Authorization'] = request.META.get('HTTP_AUTHORIZATION', '') oauth_request = oauth.OAuthRequest.from_request( - request.method, request.build_absolute_uri(), + request.method, request.build_absolute_uri(), headers=request.META, parameters=params, query_string=request.environ.get('QUERY_STRING', '')) - + if oauth_request: oauth_server = oauth.OAuthServer(oauth_datastore(oauth_request)) oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT()) oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1()) else: oauth_server = None - + return oauth_server, oauth_request def send_oauth_error(err=None): @@ -152,7 +152,7 @@ def send_oauth_error(err=None): def oauth_request_token(request): oauth_server, oauth_request = initialize_server_request(request) - + if oauth_server is None: return INVALID_PARAMS_RESPONSE try: @@ -176,20 +176,20 @@ def oauth_auth_view(request, token, callback, params): @login_required def oauth_user_auth(request): oauth_server, oauth_request = initialize_server_request(request) - + if oauth_request is None: return INVALID_PARAMS_RESPONSE - + try: token = oauth_server.fetch_request_token(oauth_request) except oauth.OAuthError, err: return send_oauth_error(err) - + try: callback = oauth_server.get_callback(oauth_request) except: callback = None - + if request.method == "GET": params = oauth_request.get_normalized_parameters() @@ -207,26 +207,26 @@ def oauth_user_auth(request): else: args = '?error=%s' % 'Access not granted by user.' print "FORM ERROR", form.errors - + if not callback: callback = getattr(settings, 'OAUTH_CALLBACK_VIEW') return get_callable(callback)(request, token) - + response = HttpResponseRedirect(callback+args) - + except oauth.OAuthError, err: response = send_oauth_error(err) else: response = HttpResponse('Action not allowed.') - + return response def oauth_access_token(request): oauth_server, oauth_request = initialize_server_request(request) - + if oauth_request is None: return INVALID_PARAMS_RESPONSE - + try: token = oauth_server.fetch_access_token(oauth_request) return HttpResponse(token.to_string()) @@ -234,7 +234,7 @@ def oauth_access_token(request): return send_oauth_error(err) INVALID_PARAMS_RESPONSE = send_oauth_error(oauth.OAuthError('Invalid request parameters.')) - + class OAuthAuthentication(object): """ OAuth authentication. Based on work by Leah Culver. @@ -242,12 +242,12 @@ class OAuthAuthentication(object): def __init__(self, realm='API'): self.realm = realm self.builder = oauth.build_authenticate_header - + def is_authenticated(self, request): """ Checks whether a means of specifying authentication is provided, and if so, if it is a valid token. - + Read the documentation on `HttpBasicAuthentication` for more information about what goes on here. """ @@ -263,14 +263,14 @@ class OAuthAuthentication(object): request.consumer = consumer request.throttle_extra = token.consumer.id return True - + return False - + def challenge(self): """ Returns a 401 response with a small bit on what OAuth is, and where to learn more about it. - + When this was written, browsers did not understand OAuth authentication on the browser side, and hence the helpful template we render. Maybe some day in the @@ -290,7 +290,7 @@ class OAuthAuthentication(object): response.content = tmpl return response - + @staticmethod def is_valid_request(request): """ @@ -302,14 +302,14 @@ class OAuthAuthentication(object): must_have = [ 'oauth_'+s for s in [ 'consumer_key', 'token', 'signature', 'signature_method', 'timestamp', 'nonce' ] ] - + is_in = lambda l: all([ (p in l) for p in must_have ]) auth_params = request.META.get("HTTP_AUTHORIZATION", "") req_params = request.REQUEST - + return is_in(auth_params) or is_in(req_params) - + @staticmethod def validate_token(request, check_timestamp=True, check_nonce=True): oauth_server, oauth_request = initialize_server_request(request) diff --git a/apps/piston/decorator.py b/apps/piston/decorator.py index f8dc3b8ee..e173d011b 100755 --- a/apps/piston/decorator.py +++ b/apps/piston/decorator.py @@ -28,7 +28,7 @@ def getinfo(func): - doc (the docstring : str) - module (the module name : str) - dict (the function __dict__ : str) - + >>> def f(self, x=1, y=2, *args, **kw): pass >>> info = getinfo(f) @@ -37,7 +37,7 @@ def getinfo(func): 'f' >>> info["argnames"] ['self', 'x', 'y', 'args', 'kw'] - + >>> info["defaults"] (1, 2) @@ -75,7 +75,7 @@ def update_wrapper(wrapper, model, infodict=None): def new_wrapper(wrapper, model): """ An improvement over functools.update_wrapper. The wrapper is a generic - callable object. It works by generating a copy of the wrapper with the + callable object. It works by generating a copy of the wrapper with the right signature and by updating the copy, not the original. Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module', 'dict', 'defaults'. @@ -126,7 +126,7 @@ def decorator(caller): def caller(func, *args, **kw): # do something return func(*args, **kw) - + Here is an example of usage: >>> @decorator @@ -136,7 +136,7 @@ def decorator(caller): >>> chatty.__name__ 'chatty' - + >>> @chatty ... def f(): pass ... @@ -164,13 +164,13 @@ if __name__ == "__main__": import doctest; doctest.testmod() ########################## LEGALESE ############################### - -## Redistributions of source code must retain the above copyright + +## Redistributions of source code must retain the above copyright ## notice, this list of conditions and the following disclaimer. ## Redistributions in bytecode form must reproduce the above copyright ## notice, this list of conditions and the following disclaimer in ## the documentation and/or other materials provided with the -## distribution. +## distribution. ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT diff --git a/apps/piston/doc.py b/apps/piston/doc.py index 63f89ecd4..fc14addb6 100644 --- a/apps/piston/doc.py +++ b/apps/piston/doc.py @@ -15,14 +15,14 @@ def generate_doc(handler_cls): """ if not type(handler_cls) is handler.HandlerMetaClass: raise ValueError("Give me handler, not %s" % type(handler_cls)) - + return HandlerDocumentation(handler_cls) - + class HandlerMethod(object): def __init__(self, method, stale=False): self.method = method self.stale = stale - + def iter_args(self): args, _, _, defaults = inspect.getargspec(self.method) @@ -36,34 +36,34 @@ class HandlerMethod(object): yield (arg, str(defaults[-didx])) else: yield (arg, None) - + @property def signature(self, parse_optional=True): spec = "" for argn, argdef in self.iter_args(): spec += argn - + if argdef: spec += '=%s' % argdef - + spec += ', ' - + spec = spec.rstrip(", ") - + if parse_optional: return spec.replace("=None", "=") - + return spec - + @property def doc(self): return inspect.getdoc(self.method) - + @property def name(self): return self.method.__name__ - + @property def http_name(self): if self.name == 'read': @@ -74,21 +74,21 @@ class HandlerMethod(object): return 'DELETE' elif self.name == 'update': return 'PUT' - + def __repr__(self): return "" % self.name - + class HandlerDocumentation(object): def __init__(self, handler): self.handler = handler - + def get_methods(self, include_default=False): for method in "read create update delete".split(): met = getattr(self.handler, method, None) if not met: continue - + stale = inspect.getmodule(met) is handler if not self.handler.is_anonymous: @@ -97,64 +97,64 @@ class HandlerDocumentation(object): else: if not stale or met.__name__ == "read" \ and 'GET' in self.allowed_methods: - + yield HandlerMethod(met, stale) - + def get_all_methods(self): return self.get_methods(include_default=True) - + @property def is_anonymous(self): return handler.is_anonymous def get_model(self): return getattr(self, 'model', None) - + @property def has_anonymous(self): return self.handler.anonymous - + @property def anonymous(self): if self.has_anonymous: return HandlerDocumentation(self.handler.anonymous) - + @property def doc(self): return self.handler.__doc__ - + @property def name(self): return self.handler.__name__ - + @property def allowed_methods(self): return self.handler.allowed_methods - + def get_resource_uri_template(self): """ URI template processor. - + See http://bitworking.org/projects/URI-Templates/ """ def _convert(template, params=[]): """URI template converter""" paths = template % dict([p, "{%s}" % p] for p in params) return u'%s%s' % (get_script_prefix(), paths) - + try: resource_uri = self.handler.resource_uri() - + components = [None, [], {}] for i, value in enumerate(resource_uri): components[i] = value - + lookup_view, args, kwargs = components lookup_view = get_callable(lookup_view, True) possibilities = get_resolver(None).reverse_dict.getlist(lookup_view) - + for possibility, pattern in possibilities: for result, params in possibility: if args: @@ -167,9 +167,9 @@ class HandlerDocumentation(object): return _convert(result, params) except: return None - + resource_uri_template = property(get_resource_uri_template) - + def __repr__(self): return u'' % self.name @@ -180,16 +180,16 @@ def documentation_view(request): """ docs = [ ] - for handler in handler_tracker: + for handler in handler_tracker: docs.append(generate_doc(handler)) - def _compare(doc1, doc2): + def _compare(doc1, doc2): #handlers and their anonymous counterparts are put next to each other. name1 = doc1.name.replace("Anonymous", "") name2 = doc2.name.replace("Anonymous", "") - return cmp(name1, name2) - + return cmp(name1, name2) + docs.sort(_compare) - - return render_to_response('documentation.html', + + return render_to_response('documentation.html', { 'docs': docs }, RequestContext(request)) diff --git a/apps/piston/emitters.py b/apps/piston/emitters.py index 1ff0a52da..c2116d22b 100644 --- a/apps/piston/emitters.py +++ b/apps/piston/emitters.py @@ -58,7 +58,7 @@ class Emitter(object): as the methods on the handler. Issue58 says that's no good. """ EMITTERS = { } - RESERVED_FIELDS = set([ 'read', 'update', 'create', + RESERVED_FIELDS = set([ 'read', 'update', 'create', 'delete', 'model', 'anonymous', 'allowed_methods', 'fields', 'exclude' ]) @@ -68,16 +68,16 @@ class Emitter(object): self.handler = handler self.fields = fields self.anonymous = anonymous - + if isinstance(self.data, Exception): raise - + def method_fields(self, handler, fields): if not handler: return { } ret = dict() - + for field in fields - Emitter.RESERVED_FIELDS: t = getattr(handler, str(field), None) @@ -85,13 +85,13 @@ class Emitter(object): ret[field] = t return ret - + def construct(self): """ Recursively serialize a lot of types, and in cases where it doesn't recognize the type, it will fall back to Django's `smart_unicode`. - + Returns `dict`. """ def _any(thing, fields=()): @@ -99,7 +99,7 @@ class Emitter(object): Dispatch, all types are routed through here. """ ret = None - + if isinstance(thing, QuerySet): ret = _qs(thing, fields=fields) elif isinstance(thing, (tuple, list)): @@ -131,19 +131,19 @@ class Emitter(object): Foreign keys. """ return _any(getattr(data, field.name)) - + def _related(data, fields=()): """ Foreign keys. """ return [ _model(m, fields) for m in data.iterator() ] - + def _m2m(data, field, fields=()): """ Many to many (re-route to `_model`.) """ return [ _model(m, fields) for m in getattr(data, field.name).iterator() ] - + def _model(data, fields=()): """ Models. Will respect the `fields` and/or @@ -152,7 +152,7 @@ class Emitter(object): ret = { } handler = self.in_typemapper(type(data), self.anonymous) get_absolute_uri = False - + if handler or fields: v = lambda f: getattr(data, f.attname) @@ -167,26 +167,26 @@ class Emitter(object): if 'absolute_uri' in get_fields: get_absolute_uri = True - + if not get_fields: get_fields = set([ f.attname.replace("_id", "", 1) for f in data._meta.fields ]) - + # sets can be negated. for exclude in exclude_fields: if isinstance(exclude, basestring): get_fields.discard(exclude) - + elif isinstance(exclude, re._pattern_type): for field in get_fields.copy(): if exclude.match(field): get_fields.discard(field) - + else: get_fields = set(fields) met_fields = self.method_fields(handler, get_fields) - + for f in data._meta.local_fields: if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): if not f.rel: @@ -197,13 +197,13 @@ class Emitter(object): if f.attname[:-3] in get_fields: ret[f.name] = _fk(data, f) get_fields.remove(f.name) - + for mf in data._meta.many_to_many: if mf.serialize and mf.attname not in met_fields: if mf.attname in get_fields: ret[mf.name] = _m2m(data, mf) get_fields.remove(mf.name) - + # try to get the remainder of fields for maybe_field in get_fields: if isinstance(maybe_field, (list, tuple)): @@ -225,7 +225,7 @@ class Emitter(object): # using different names. ret[maybe_field] = _any(met_fields[maybe_field](data)) - else: + else: maybe = getattr(data, maybe_field, None) if maybe: if callable(maybe): @@ -242,13 +242,13 @@ class Emitter(object): else: for f in data._meta.fields: ret[f.attname] = _any(getattr(data, f.attname)) - + fields = dir(data.__class__) + ret.keys() add_ons = [k for k in dir(data) if k not in fields] - + for k in add_ons: ret[k] = _any(getattr(data, k)) - + # resouce uri if self.in_typemapper(type(data), self.anonymous): handler = self.in_typemapper(type(data), self.anonymous) @@ -259,51 +259,51 @@ class Emitter(object): ret['resource_uri'] = reverser( lambda: (url_id, fields) )() except NoReverseMatch, e: pass - + if hasattr(data, 'get_api_url') and 'resource_uri' not in ret: try: ret['resource_uri'] = data.get_api_url() except: pass - + # absolute uri if hasattr(data, 'get_absolute_url') and get_absolute_uri: try: ret['absolute_uri'] = data.get_absolute_url() except: pass - + return ret - + def _qs(data, fields=()): """ Querysets. """ return [ _any(v, fields) for v in data ] - + def _list(data): """ Lists. """ return [ _any(v) for v in data ] - + def _dict(data): """ Dictionaries. """ return dict([ (k, _any(v)) for k, v in data.iteritems() ]) - + # Kickstart the seralizin'. return _any(self.data, self.fields) - + def in_typemapper(self, model, anonymous): for klass, (km, is_anon) in self.typemapper.iteritems(): if model is km and is_anon is anonymous: return klass - + def render(self): """ This super emitter does not implement `render`, this is a job for the specific emitter below. """ raise NotImplementedError("Please implement render.") - + def stream_render(self, request, stream=True): """ Tells our patched middleware not to look @@ -312,7 +312,7 @@ class Emitter(object): more memory friendly for large datasets. """ yield self.render(request) - + @classmethod def get(cls, format): """ @@ -322,19 +322,19 @@ class Emitter(object): return cls.EMITTERS.get(format) raise ValueError("No emitters found for type %s" % format) - + @classmethod def register(cls, name, klass, content_type='text/plain'): """ Register an emitter. - + Parameters:: - `name`: The name of the emitter ('json', 'xml', 'yaml', ...) - `klass`: The emitter class. - `content_type`: The content type to serve response as. """ cls.EMITTERS[name] = (klass, content_type) - + @classmethod def unregister(cls, name): """ @@ -342,7 +342,7 @@ class Emitter(object): want to provide output in one of the built-in emitters. """ return cls.EMITTERS.pop(name, None) - + class XMLEmitter(Emitter): def _to_xml(self, xml, data): if isinstance(data, (list, tuple)): @@ -360,16 +360,16 @@ class XMLEmitter(Emitter): def render(self, request): stream = StringIO.StringIO() - + xml = SimplerXMLGenerator(stream, "utf-8") xml.startDocument() xml.startElement("response", {}) - + self._to_xml(xml, self.construct()) - + xml.endElement("response") xml.endDocument() - + return stream.getvalue() Emitter.register('xml', XMLEmitter, 'text/xml; charset=utf-8') @@ -388,10 +388,10 @@ class JSONEmitter(Emitter): return '%s(%s)' % (cb, seria) return seria - + Emitter.register('json', JSONEmitter, 'application/json; charset=utf-8') Mimer.register(simplejson.loads, ('application/json',)) - + class YAMLEmitter(Emitter): """ YAML emitter, uses `safe_dump` to omit the @@ -410,7 +410,7 @@ class PickleEmitter(Emitter): """ def render(self, request): return pickle.dumps(self.construct()) - + Emitter.register('pickle', PickleEmitter, 'application/python-pickle') """ @@ -437,5 +437,5 @@ class DjangoEmitter(Emitter): response = serializers.serialize(format, self.data, indent=True) return response - + Emitter.register('django', DjangoEmitter, 'text/xml; charset=utf-8') diff --git a/apps/piston/forms.py b/apps/piston/forms.py index 351df7c50..36bd6ff93 100644 --- a/apps/piston/forms.py +++ b/apps/piston/forms.py @@ -5,7 +5,7 @@ from django.conf import settings class Form(forms.Form): pass - + class ModelForm(forms.ModelForm): """ Subclass of `forms.ModelForm` which makes sure diff --git a/apps/piston/handler.py b/apps/piston/handler.py index 2d28bb3d5..a128d7afe 100644 --- a/apps/piston/handler.py +++ b/apps/piston/handler.py @@ -19,17 +19,17 @@ class HandlerMetaClass(type): for k, (m, a) in typemapper.iteritems(): if model == m and anon == a: return k - + if hasattr(new_cls, 'model'): if already_registered(new_cls.model, new_cls.is_anonymous): if not getattr(settings, 'PISTON_IGNORE_DUPE_MODELS', False): warnings.warn("Handler already registered for model %s, " "you may experience inconsistent results." % new_cls.model.__name__) - + typemapper[new_cls] = (new_cls.model, new_cls.is_anonymous) else: typemapper[new_cls] = (None, new_cls.is_anonymous) - + if name not in ('BaseHandler', 'AnonymousBaseHandler'): handler_tracker.append(new_cls) @@ -40,43 +40,43 @@ class BaseHandler(object): Basehandler that gives you CRUD for free. You are supposed to subclass this for specific functionality. - + All CRUD methods (`read`/`update`/`create`/`delete`) receive a request as the first argument from the resource. Use this for checking `request.user`, etc. """ __metaclass__ = HandlerMetaClass - + allowed_methods = ('GET', 'POST', 'PUT', 'DELETE') anonymous = is_anonymous = False exclude = ( 'id', ) fields = ( ) - + def flatten_dict(self, dct): return dict([ (str(k), dct.get(k)) for k in dct.keys() ]) - + def has_model(self): return hasattr(self, 'model') or hasattr(self, 'queryset') def queryset(self, request): return self.model.objects.all() - + def value_from_tuple(tu, name): for int_, n in tu: if n == name: return int_ return None - + def exists(self, **kwargs): if not self.has_model(): raise NotImplementedError - + try: self.model.objects.get(**kwargs) return True except self.model.DoesNotExist: return False - + def read(self, request, *args, **kwargs): if not self.has_model(): return rc.NOT_IMPLEMENTED @@ -92,13 +92,13 @@ class BaseHandler(object): return rc.BAD_REQUEST else: return self.queryset(request).filter(*args, **kwargs) - + def create(self, request, *args, **kwargs): if not self.has_model(): return rc.NOT_IMPLEMENTED - + attrs = self.flatten_dict(request.POST) - + try: inst = self.queryset(request).get(**attrs) return rc.DUPLICATE_ENTRY @@ -108,7 +108,7 @@ class BaseHandler(object): return inst except self.model.MultipleObjectsReturned: return rc.DUPLICATE_ENTRY - + def update(self, request, *args, **kwargs): if not self.has_model(): return rc.NOT_IMPLEMENTED @@ -132,7 +132,7 @@ class BaseHandler(object): inst.save() return rc.ALL_OK - + def delete(self, request, *args, **kwargs): if not self.has_model(): raise NotImplementedError @@ -147,7 +147,7 @@ class BaseHandler(object): return rc.DUPLICATE_ENTRY except self.model.DoesNotExist: return rc.NOT_HERE - + class AnonymousBaseHandler(BaseHandler): """ Anonymous handler. diff --git a/apps/piston/managers.py b/apps/piston/managers.py index 5694d0e28..a1c9d496b 100644 --- a/apps/piston/managers.py +++ b/apps/piston/managers.py @@ -48,15 +48,15 @@ class ResourceManager(models.Manager): if not self._default_resource: self._default_resource = self.get(name=name) - return self._default_resource + return self._default_resource class TokenManager(KeyManager): def create_token(self, consumer, token_type, timestamp, user=None): """ Shortcut to create a token with random key/secret. """ - token, created = self.get_or_create(consumer=consumer, - token_type=token_type, + token, created = self.get_or_create(consumer=consumer, + token_type=token_type, timestamp=timestamp, user=user) @@ -65,4 +65,4 @@ class TokenManager(KeyManager): token.save() return token - + diff --git a/apps/piston/models.py b/apps/piston/models.py index 9c93af85d..d205930ea 100644 --- a/apps/piston/models.py +++ b/apps/piston/models.py @@ -29,7 +29,7 @@ class Nonce(models.Model): token_key = models.CharField(max_length=KEY_SIZE) consumer_key = models.CharField(max_length=KEY_SIZE) key = models.CharField(max_length=255) - + def __unicode__(self): return u"Nonce %s for %s" % (self.key, self.consumer_key) @@ -46,17 +46,17 @@ class Consumer(models.Model): user = models.ForeignKey(User, null=True, blank=True, related_name='consumers') objects = ConsumerManager() - + def __unicode__(self): return u"Consumer %s with key %s" % (self.name, self.key) def generate_random_codes(self): """ Used to generate random key/secret pairings. Use this after you've - added the other data in place of save(). + added the other data in place of save(). c = Consumer() - c.name = "My consumer" + c.name = "My consumer" c.description = "An app that makes ponies from the API." c.user = some_user_object c.generate_random_codes() @@ -77,28 +77,28 @@ class Token(models.Model): REQUEST = 1 ACCESS = 2 TOKEN_TYPES = ((REQUEST, u'Request'), (ACCESS, u'Access')) - + key = models.CharField(max_length=KEY_SIZE) secret = models.CharField(max_length=SECRET_SIZE) verifier = models.CharField(max_length=VERIFIER_SIZE) token_type = models.IntegerField(choices=TOKEN_TYPES) timestamp = models.IntegerField(default=long(time.time())) is_approved = models.BooleanField(default=False) - + user = models.ForeignKey(User, null=True, blank=True, related_name='tokens') consumer = models.ForeignKey(Consumer) - + callback = models.CharField(max_length=255, null=True, blank=True) callback_confirmed = models.BooleanField(default=False) - + objects = TokenManager() - + def __unicode__(self): return u"%s Token %s for %s" % (self.get_token_type_display(), self.key, self.consumer) def to_string(self, only_key=False): token_dict = { - 'oauth_token': self.key, + 'oauth_token': self.key, 'oauth_token_secret': self.secret, 'oauth_callback_confirmed': 'true', } @@ -121,7 +121,7 @@ class Token(models.Model): self.key = key self.secret = secret self.save() - + # -- OAuth 1.0a stuff def get_callback_url(self): @@ -136,13 +136,13 @@ class Token(models.Model): return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) return self.callback - + def set_callback(self, callback): if callback != "oob": # out of band, says "we can't do this!" self.callback = callback self.callback_confirmed = True self.save() - + admin.site.register(Token) # Attach our signals diff --git a/apps/piston/oauth.py b/apps/piston/oauth.py index 3a42e20f9..8c430ea7a 100644 --- a/apps/piston/oauth.py +++ b/apps/piston/oauth.py @@ -87,7 +87,7 @@ class OAuthConsumer(object): class OAuthToken(object): """OAuthToken is a data type that represents an End User via either an access or request token. - + key -- the token secret -- the token secret @@ -133,7 +133,7 @@ class OAuthToken(object): if self.callback_confirmed is not None: data['oauth_callback_confirmed'] = self.callback_confirmed return urllib.urlencode(data) - + def from_string(s): """ Returns a token from something like: oauth_token_secret=xxx&oauth_token=xxx @@ -157,11 +157,11 @@ class OAuthRequest(object): """OAuthRequest represents the request and can be serialized. OAuth parameters: - - oauth_consumer_key + - oauth_consumer_key - oauth_token - oauth_signature_method - - oauth_signature - - oauth_timestamp + - oauth_signature + - oauth_timestamp - oauth_nonce - oauth_version - oauth_verifier @@ -436,7 +436,7 @@ class OAuthServer(object): def get_callback(self, oauth_request): """Get the callback URL.""" return oauth_request.get_parameter('oauth_callback') - + def build_authenticate_header(self, realm=''): """Optional support for the authenticate header.""" return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} @@ -482,7 +482,7 @@ class OAuthServer(object): if not token: raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) return token - + def _get_verifier(self, oauth_request): return oauth_request.get_parameter('oauth_verifier') @@ -601,7 +601,7 @@ class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): def get_name(self): return 'HMAC-SHA1' - + def build_signature_base_string(self, oauth_request, consumer, token): sig = ( escape(oauth_request.get_normalized_http_method()), diff --git a/apps/piston/resource.py b/apps/piston/resource.py index 40f065d05..ee78b0e77 100644 --- a/apps/piston/resource.py +++ b/apps/piston/resource.py @@ -26,22 +26,22 @@ class Resource(object): is an authentication handler. If not specified, `NoAuthentication` will be used by default. """ - callmap = { 'GET': 'read', 'POST': 'create', + callmap = { 'GET': 'read', 'POST': 'create', 'PUT': 'update', 'DELETE': 'delete' } - + def __init__(self, handler, authentication=None): if not callable(handler): raise AttributeError, "Handler not callable." - + self.handler = handler() - + if not authentication: self.authentication = (NoAuthentication(),) elif isinstance(authentication, (list, tuple)): self.authentication = authentication else: self.authentication = (authentication,) - + # Erroring self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True) self.display_errors = getattr(settings, 'PISTON_DISPLAY_ERRORS', True) @@ -58,12 +58,12 @@ class Resource(object): that as well. """ em = kwargs.pop('emitter_format', None) - + if not em: em = request.GET.get('format', 'json') return em - + @property def anonymous(self): """ @@ -74,16 +74,16 @@ class Resource(object): """ if hasattr(self.handler, 'anonymous'): anon = self.handler.anonymous - + if callable(anon): return anon for klass in typemapper.keys(): if anon == klass.__name__: return klass - + return None - + def authenticate(self, request, rm): actor, anonymous = False, True @@ -97,9 +97,9 @@ class Resource(object): actor, anonymous = authenticator.challenge, CHALLENGE else: return self.handler, self.handler.is_anonymous - + return actor, anonymous - + @vary_on_headers('Authorization') def __call__(self, request, *args, **kwargs): """ @@ -119,19 +119,19 @@ class Resource(object): return actor() else: handler = actor - + # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST - + if not rm in handler.allowed_methods: return HttpResponseNotAllowed(handler.allowed_methods) - + meth = getattr(handler, self.callmap.get(rm), None) - + if not meth: raise Http404 @@ -139,18 +139,18 @@ class Resource(object): em_format = self.determine_emitter(request, *args, **kwargs) kwargs.pop('emitter_format', None) - + # Clean up the request object a bit, since we might # very well have `oauth_`-headers in there, and we # don't want to pass these along to the handler. request = self.cleanup_request(request) - + try: result = meth(request, *args, **kwargs) except FormValidationError, e: resp = rc.BAD_REQUEST resp.write(' '+str(e.form.errors)) - + return resp except TypeError, e: result = rc.BAD_REQUEST @@ -158,15 +158,15 @@ class Resource(object): sig = hm.signature msg = 'Method signature does not match.\n\n' - + if sig: msg += 'Signature should be: %s' % sig else: msg += 'Resource does not expect any parameters.' - if self.display_errors: + if self.display_errors: msg += '\n\nException was: %s' % str(e) - + result.content = format_error(msg) except Http404: return rc.NOT_FOUND @@ -177,13 +177,13 @@ class Resource(object): On errors (like code errors), we'd like to be able to give crash reports to both admins and also the calling user. There's two setting parameters for this: - + Parameters:: - `PISTON_EMAIL_ERRORS`: Will send a Django formatted error email to people in `settings.ADMINS`. - `PISTON_DISPLAY_ERRORS`: Will return a simple traceback to the caller, so he can tell you what error they got. - + If `PISTON_DISPLAY_ERRORS` is not enabled, the caller will receive a basic "500 Internal Server Error" message. """ @@ -237,17 +237,17 @@ class Resource(object): if True in [ k.startswith("oauth_") for k in block.keys() ]: sanitized = block.copy() - + for k in sanitized.keys(): if k.startswith("oauth_"): sanitized.pop(k) - + setattr(request, method_type, sanitized) return request - - # -- - + + # -- + def email_exception(self, reporter): subject = "Piston crash report" html = reporter.get_traceback_html() @@ -255,6 +255,6 @@ class Resource(object): message = EmailMessage(settings.EMAIL_SUBJECT_PREFIX+subject, html, settings.SERVER_EMAIL, [ admin[1] for admin in settings.ADMINS ]) - + message.content_subtype = 'html' message.send(fail_silently=True) diff --git a/apps/piston/signals.py b/apps/piston/signals.py index 133be13fe..a302d1b81 100644 --- a/apps/piston/signals.py +++ b/apps/piston/signals.py @@ -1,5 +1,5 @@ # Django imports -import django.dispatch +import django.dispatch # Piston imports from utils import send_consumer_mail diff --git a/apps/piston/store.py b/apps/piston/store.py index 787791ab8..13377d6a9 100644 --- a/apps/piston/store.py +++ b/apps/piston/store.py @@ -23,7 +23,7 @@ class DataStore(oauth.OAuthDataStore): elif token_type == 'access': token_type = Token.ACCESS try: - self.request_token = Token.objects.get(key=token, + self.request_token = Token.objects.get(key=token, token_type=token_type) return self.request_token except Token.DoesNotExist: @@ -32,7 +32,7 @@ class DataStore(oauth.OAuthDataStore): def lookup_nonce(self, oauth_consumer, oauth_token, nonce): if oauth_token is None: return None - nonce, created = Nonce.objects.get_or_create(consumer_key=oauth_consumer.key, + nonce, created = Nonce.objects.get_or_create(consumer_key=oauth_consumer.key, token_key=oauth_token.key, key=nonce) if created: @@ -45,10 +45,10 @@ class DataStore(oauth.OAuthDataStore): self.request_token = Token.objects.create_token(consumer=self.consumer, token_type=Token.REQUEST, timestamp=self.timestamp) - + if oauth_callback: self.request_token.set_callback(oauth_callback) - + return self.request_token return None diff --git a/apps/piston/templates/documentation.html b/apps/piston/templates/documentation.html index d7b18300d..8fdfb8f8e 100644 --- a/apps/piston/templates/documentation.html +++ b/apps/piston/templates/documentation.html @@ -16,40 +16,40 @@

API Documentation

- + {% for doc in docs %} - +

{{ doc.name|cut:"Handler" }}:

{{ doc.get_doc|default:""|restructuredtext }}

- +

URL: {{ doc.get_resource_uri_template }}

- +

Accepted methods: {% for meth in doc.allowed_methods %}{{ meth }}{% if not forloop.last %}, {% endif %}{% endfor %}

- +
{% for method in doc.get_all_methods %} - +
method {{ method.name }}({{ method.signature }}){% if method.stale %} - inherited{% else %}:{% endif %} - -
- + + + {% if method.get_doc %}
{{ method.get_doc|default:""|restructuredtext }}
{% endif %} - + {% endfor %}
- + {% endfor %} diff --git a/apps/piston/templates/piston/authorize_token.html b/apps/piston/templates/piston/authorize_token.html index dae840e02..59c09e331 100644 --- a/apps/piston/templates/piston/authorize_token.html +++ b/apps/piston/templates/piston/authorize_token.html @@ -6,7 +6,7 @@

Authorize Token

- +
{{ form.as_table }}
diff --git a/apps/piston/test.py b/apps/piston/test.py index 57eda72be..f19fb213a 100644 --- a/apps/piston/test.py +++ b/apps/piston/test.py @@ -32,8 +32,8 @@ class OAuthClient(client.Client): url = "http://testserver" + request['PATH_INFO'] req = oauth.OAuthRequest.from_consumer_and_token( - self.consumer, token=self.token, - http_method=request['REQUEST_METHOD'], http_url=url, + self.consumer, token=self.token, + http_method=request['REQUEST_METHOD'], http_url=url, parameters=params ) @@ -49,7 +49,7 @@ class OAuthClient(client.Client): if isinstance(data, dict): data = urlencode(data) - + return super(OAuthClient, self).post(path, data, content_type, follow, **extra) class TestCase(test.TestCase): diff --git a/apps/piston/tests.py b/apps/piston/tests.py index 92d14ee3b..99f7028fd 100644 --- a/apps/piston/tests.py +++ b/apps/piston/tests.py @@ -19,7 +19,7 @@ class ConsumerTest(TestCase): def test_create_pending(self): """ Ensure creating a pending Consumer sends proper emails """ - # If it's pending we should have two messages in the outbox; one + # If it's pending we should have two messages in the outbox; one # to the consumer and one to the site admins. if len(settings.ADMINS): self.assertEquals(len(mail.outbox), 2) @@ -36,8 +36,8 @@ class ConsumerTest(TestCase): mail.outbox = [] # Delete the consumer, which should fire off the cancel email. - self.consumer.delete() - + self.consumer.delete() + self.assertEquals(len(mail.outbox), 1) expected = "Your API Consumer for example.com has been canceled." self.assertEquals(mail.outbox[0].subject, expected) diff --git a/apps/piston/utils.py b/apps/piston/utils.py index 8d0cce8fd..9128cbc3f 100644 --- a/apps/piston/utils.py +++ b/apps/piston/utils.py @@ -39,7 +39,7 @@ class rc_factory(object): def __getattr__(self, attr): """ - Returns a fresh `HttpResponse` when getting + Returns a fresh `HttpResponse` when getting an "attribute". This is backwards compatible with 0.2, which is important. """ @@ -49,9 +49,9 @@ class rc_factory(object): raise AttributeError(attr) return HttpResponse(r, content_type='text/plain', status=c) - + rc = rc_factory() - + class FormValidationError(Exception): def __init__(self, form): self.form = form @@ -64,7 +64,7 @@ def validate(v_form, operation='POST'): @decorator def wrap(f, self, request, *a, **kwa): form = v_form(getattr(request, operation)) - + if form.is_valid(): return f(self, request, *a, **kwa) else: @@ -75,11 +75,11 @@ def throttle(max_requests, timeout=60*60, extra=''): """ Simple throttling decorator, caches the amount of requests made in cache. - + If used on a view where users are required to log in, the username is used, otherwise the IP address of the originating request is used. - + Parameters:: - `max_requests`: The maximum number of requests - `timeout`: The timeout for the cache entry (default: 1 hour) @@ -90,7 +90,7 @@ def throttle(max_requests, timeout=60*60, extra=''): ident = request.user.username else: ident = request.META.get('REMOTE_ADDR', None) - + if hasattr(request, 'throttle_extra'): """ Since we want to be able to throttle on a per- @@ -99,7 +99,7 @@ def throttle(max_requests, timeout=60*60, extra=''): object. If so, append the identifier name with it. """ ident += ':%s' % str(request.throttle_extra) - + if ident: """ Preferrably we'd use incr/decr here, since they're @@ -108,7 +108,7 @@ def throttle(max_requests, timeout=60*60, extra=''): stable, you can change it here. """ ident += ':%s' % extra - + now = time.time() count, expiration = cache.get(ident, (1, None)) @@ -123,7 +123,7 @@ def throttle(max_requests, timeout=60*60, extra=''): return t cache.set(ident, (count+1, expiration), (expiration - now)) - + return f(self, request, *args, **kwargs) return wrap @@ -133,7 +133,7 @@ def coerce_put_post(request): In case we send data over PUT, Django won't actually look at the data and load it. We need to twist its arm here. - + The try/except abominiation here is due to a bug in mod_python. This should fix it. """ @@ -146,7 +146,7 @@ def coerce_put_post(request): request.META['REQUEST_METHOD'] = 'POST' request._load_post_and_files() request.META['REQUEST_METHOD'] = 'PUT' - + request.PUT = request.POST @@ -158,10 +158,10 @@ class MimerDataException(Exception): class Mimer(object): TYPES = dict() - + def __init__(self, request): self.request = request - + def is_multipart(self): content_type = self.content_type() @@ -179,7 +179,7 @@ class Mimer(object): for mime in mimes: if ctype.startswith(mime): return loadee - + def content_type(self): """ Returns the content type of the request in all cases where it is @@ -188,10 +188,10 @@ class Mimer(object): type_formencoded = "application/x-www-form-urlencoded" ctype = self.request.META.get('CONTENT_TYPE', type_formencoded) - + if type_formencoded in ctype: return None - + return ctype def translate(self): @@ -202,21 +202,21 @@ class Mimer(object): key-value (and maybe just a list), the data will be placed on `request.data` instead, and the handler will have to read from there. - + It will also set `request.content_type` so the handler has an easy way to tell what's going on. `request.content_type` will always be None for form-encoded and/or multipart form data (what your browser sends.) - """ + """ ctype = self.content_type() self.request.content_type = ctype - + if not self.is_multipart() and ctype: loadee = self.loader_for_type(ctype) - + if loadee: try: self.request.data = loadee(self.request.raw_post_data) - + # Reset both POST and PUT from request, as its # misleading having their presence around. self.request.POST = self.request.PUT = dict() @@ -227,18 +227,18 @@ class Mimer(object): self.request.data = None return self.request - + @classmethod def register(cls, loadee, types): cls.TYPES[loadee] = types - + @classmethod def unregister(cls, loadee): return cls.TYPES.pop(loadee) def translate_mime(request): request = Mimer(request).translate() - + def require_mime(*mimes): """ Decorator requiring a certain mimetype. There's a nifty @@ -265,7 +265,7 @@ def require_mime(*mimes): return wrap require_extended = require_mime('json', 'yaml', 'xml', 'pickle') - + def send_consumer_mail(consumer): """ Send a consumer an email depending on what their status is. @@ -280,20 +280,20 @@ def send_consumer_mail(consumer): subject += "has been canceled." elif consumer.status == "rejected": subject += "has been rejected." - else: + else: subject += "is awaiting approval." - template = "piston/mails/consumer_%s.txt" % consumer.status - + template = "piston/mails/consumer_%s.txt" % consumer.status + try: - body = loader.render_to_string(template, + body = loader.render_to_string(template, { 'consumer' : consumer, 'user' : consumer.user }) except TemplateDoesNotExist: - """ + """ They haven't set up the templates, which means they might not want these emails sent. """ - return + return try: sender = settings.PISTON_FROM_EMAIL diff --git a/apps/sponsors/admin.py b/apps/sponsors/admin.py index f8e280104..897c22159 100644 --- a/apps/sponsors/admin.py +++ b/apps/sponsors/admin.py @@ -19,7 +19,7 @@ class SponsorAdmin(admin.ModelAdmin): class SponsorPageAdmin(admin.ModelAdmin): formfield_overrides = { fields.JSONField: {'widget': widgets.SponsorPageWidget}, - } + } list_display = ('name',) search_fields = ('name',) ordering = ('name',) diff --git a/apps/sponsors/fields.py b/apps/sponsors/fields.py index b3fa36124..c2098ec97 100644 --- a/apps/sponsors/fields.py +++ b/apps/sponsors/fields.py @@ -32,7 +32,7 @@ def loads(str): class JSONFormField(forms.CharField): widget = forms.Textarea - + def clean(self, value): try: loads(value) @@ -55,7 +55,7 @@ class JSONField(models.TextField): def contribute_to_class(self, cls, name): super(JSONField, self).contribute_to_class(cls, name) - + def get_value(model_instance): return loads(getattr(model_instance, self.attname, None)) setattr(cls, 'get_%s_value' % self.name, get_value) diff --git a/apps/sponsors/migrations/0001_initial.py b/apps/sponsors/migrations/0001_initial.py index 5721bcc72..daccec53b 100644 --- a/apps/sponsors/migrations/0001_initial.py +++ b/apps/sponsors/migrations/0001_initial.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'Sponsor' db.create_table('sponsors_sponsor', ( ('url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)), @@ -26,17 +26,17 @@ class Migration(SchemaMigration): ('name', self.gf('django.db.models.fields.CharField')(max_length=120)), )) db.send_create_signal('sponsors', ['SponsorPage']) - - + + def backwards(self, orm): - + # Deleting model 'Sponsor' db.delete_table('sponsors_sponsor') # Deleting model 'SponsorPage' db.delete_table('sponsors_sponsorpage') - - + + models = { 'sponsors.sponsor': { 'Meta': {'object_name': 'Sponsor'}, @@ -54,5 +54,5 @@ class Migration(SchemaMigration): 'sponsors': ('sponsors.fields.JSONField', [], {'default': '{}'}) } } - + complete_apps = ['sponsors'] diff --git a/apps/sponsors/models.py b/apps/sponsors/models.py index 28ef76eee..3c2ca0247 100644 --- a/apps/sponsors/models.py +++ b/apps/sponsors/models.py @@ -22,7 +22,7 @@ class Sponsor(models.Model): 'options': ['pad', 'detail'], }) url = models.URLField(_('url'), blank=True, verify_exists=False) - + def __unicode__(self): return self.name @@ -37,7 +37,7 @@ class SponsorPage(models.Model): name = models.CharField(_('name'), max_length=120) sponsors = JSONField(_('sponsors'), default={}) _html = models.TextField(blank=True, editable=False) - + def populated_sponsors(self): result = [] for column in self.get_sponsors_value(): @@ -50,7 +50,7 @@ class SponsorPage(models.Model): pass result.append(result_group) return result - + def html(self): return self._html html = property(fget=html) @@ -60,7 +60,7 @@ class SponsorPage(models.Model): 'sponsors': self.populated_sponsors(), }) return super(SponsorPage, self).save(*args, **kwargs) - + def __unicode__(self): return self.name diff --git a/apps/sponsors/static/sponsors/js/footer_admin.js b/apps/sponsors/static/sponsors/js/footer_admin.js index 4cb5eb68c..33c794a3d 100644 --- a/apps/sponsors/static/sponsors/js/footer_admin.js +++ b/apps/sponsors/static/sponsors/js/footer_admin.js @@ -4,12 +4,12 @@ sponsors: [] }; $.extend(settings, options); - + var input = $(this).hide(); - + var container = $('
').appendTo(input.parent()); var groups = $.evalJSON(input.val()); - + var unusedDiv = $('
') .appendTo(container) .append('

dostępni sponsorzy

'); @@ -18,13 +18,13 @@ .sortable({ connectWith: '.sponsors-sponsor-group-list' }); - + // Edit group name inline function editNameInline(name) { name.unbind('click.sponsorsFooter'); var inlineInput = $('').val(name.html()); name.html(''); - + function endEditing() { name.html(inlineInput.val()); inlineInput.remove(); @@ -34,11 +34,11 @@ input.parents('form').unbind('submit.sponsorsFooter', endEditing); return false; } - + inlineInput.appendTo(name).focus().blur(endEditing); input.parents('form').bind('submit.sponsorsFooter', endEditing); } - + // Remove sponsor with passed id from sponsors array and return it function popSponsor(id) { for (var i=0; i < settings.sponsors.length; i++) { @@ -50,15 +50,15 @@ } return null; } - + // Create sponsor group and bind events function createGroup(name, sponsors) { if (!sponsors) { sponsors = []; } - + var groupDiv = $('
'); - + $('X') .click(function() { groupDiv.fadeOut('slow', function() { @@ -66,19 +66,19 @@ groupDiv.remove(); }); }).appendTo(groupDiv); - + $('

' + name + '

') .bind('click.sponsorsFooter', function() { editNameInline($(this)); }).appendTo(groupDiv); - + var groupList = $('
    ') .appendTo(groupDiv) .sortable({ connectWith: '.sponsors-sponsor-group-list' }); - - + + for (var i = 0; i < sponsors.length; i++) { $('
  1. ' + sponsors[i].name + '
  2. ') .data('obj_id', sponsors[i].id) @@ -86,12 +86,12 @@ } return groupDiv; } - + // Create groups from data in input value for (var i = 0; i < groups.length; i++) { var group = groups[i]; var sponsors = []; - + for (var j = 0; j < group.sponsors.length; j++) { var s = popSponsor(group.sponsors[j]); if (s) { @@ -100,7 +100,7 @@ } createGroup(group.name, sponsors).appendTo(container); } - + // Serialize input value before submiting form input.parents('form').submit(function(event) { var groups = []; @@ -113,19 +113,19 @@ }); input.val($.toJSON(groups)); }); - + for (i = 0; i < settings.sponsors.length; i++) { $('
  3. ' + settings.sponsors[i].name + '
  4. ') .data('obj_id', settings.sponsors[i].id) .appendTo(unusedList); } - + $('') .click(function() { var newGroup = createGroup('').appendTo(container); editNameInline($('.sponsors-sponsor-group-name', newGroup)); }).prependTo(input.parent()); - + input.parent().append('
    '); }; })(jQuery); diff --git a/apps/sponsors/templatetags/sponsor_tags.py b/apps/sponsors/templatetags/sponsor_tags.py index fb8e6b358..367012362 100644 --- a/apps/sponsors/templatetags/sponsor_tags.py +++ b/apps/sponsors/templatetags/sponsor_tags.py @@ -17,5 +17,5 @@ def sponsor_page(name): except: return u'' return mark_safe(page.html) - + sponsor_page = register.simple_tag(sponsor_page) diff --git a/apps/sponsors/widgets.py b/apps/sponsors/widgets.py index 70ddde610..e4b30bbbc 100644 --- a/apps/sponsors/widgets.py +++ b/apps/sponsors/widgets.py @@ -28,6 +28,6 @@ class SponsorPageWidget(forms.Textarea): output.append(u'\n' % + output.append(u'$("#id_%s").sponsorsFooter({sponsors: [%s]}); });\n' % (name, sponsors_js)) return mark_safe(u''.join(output)) diff --git a/apps/suggest/migrations/0001_initial.py b/apps/suggest/migrations/0001_initial.py index 4c4a118f9..a25297a7f 100644 --- a/apps/suggest/migrations/0001_initial.py +++ b/apps/suggest/migrations/0001_initial.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'Suggestion' db.create_table('suggest_suggestion', ( ('description', self.gf('django.db.models.fields.TextField')(blank=True)), @@ -18,14 +18,14 @@ class Migration(SchemaMigration): ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), )) db.send_create_signal('suggest', ['Suggestion']) - - + + def backwards(self, orm): - + # Deleting model 'Suggestion' db.delete_table('suggest_suggestion') - - + + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, @@ -73,5 +73,5 @@ class Migration(SchemaMigration): 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) } } - + complete_apps = ['suggest'] diff --git a/apps/suggest/models.py b/apps/suggest/models.py index f30387296..e24afaeb8 100644 --- a/apps/suggest/models.py +++ b/apps/suggest/models.py @@ -12,12 +12,11 @@ class Suggestion(models.Model): created_at = models.DateTimeField(_('creation date'), auto_now=True) ip = models.IPAddressField(_('IP address')) user = models.ForeignKey(User, blank=True, null=True) - + class Meta: ordering = ('-created_at',) verbose_name = _('suggestion') verbose_name_plural = _('suggestions') - + def __unicode__(self): return unicode(self.created_at) - \ No newline at end of file diff --git a/apps/suggest/urls.py b/apps/suggest/urls.py index 44d606e6b..d5982cf71 100644 --- a/apps/suggest/urls.py +++ b/apps/suggest/urls.py @@ -7,7 +7,7 @@ from django.views.generic.simple import direct_to_template from suggest.forms import SuggestForm urlpatterns = patterns('', - url(r'^$', 'django.views.generic.simple.direct_to_template', + url(r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'suggest.html', 'extra_context': {'form': SuggestForm }}, name='suggest'), url(r'^wyslij/$', 'suggest.views.report', name='report'), ) diff --git a/apps/suggest/views.py b/apps/suggest/views.py index 4ad17046d..5d007f6df 100644 --- a/apps/suggest/views.py +++ b/apps/suggest/views.py @@ -11,7 +11,7 @@ from suggest import forms from suggest.models import Suggestion # FIXME - shouldn't be in catalogue -from catalogue.views import LazyEncoder +from catalogue.views import LazyEncoder #@require_POST diff --git a/fabfile.py b/fabfile.py index 13051778d..1995fd3cb 100644 --- a/fabfile.py +++ b/fabfile.py @@ -21,7 +21,7 @@ def staging(): env.python = '/usr/bin/python' env.virtualenv = '/usr/bin/virtualenv' env.pip = '/usr/bin/pip' - + def production(): """Use production server""" env.hosts = ['wolnelektury.pl:22123'] @@ -54,8 +54,8 @@ def setup(): def deploy(): """ - Deploy the latest version of the site to the servers, - install any required third party modules, + Deploy the latest version of the site to the servers, + install any required third party modules, install the virtual host and then restart the webserver """ require('hosts', 'path', provided_by=[staging, production]) diff --git a/lib/librarian b/lib/librarian new file mode 160000 index 000000000..d43d87400 --- /dev/null +++ b/lib/librarian @@ -0,0 +1 @@ +Subproject commit d43d87400dcc19851442a42ff20af9fc0b549087 diff --git a/lib/markupstring.py b/lib/markupstring.py index 2ecf4cf97..0e273f2a1 100644 --- a/lib/markupstring.py +++ b/lib/markupstring.py @@ -7,27 +7,27 @@ import xml.sax class simpleHandler(xml.sax.ContentHandler): """A simple handler that provides us with indices of marked up content.""" - def __init__(self): + def __init__(self): self.elements = [] #this will contain a list of elements and their start/end indices self.open_elements = [] #this holds info on open elements while we wait for their close self.content = "" - def startElement(self,name,attrs): - if name=='foobar': return # we require an outer wrapper, which we promptly ignore. + def startElement(self, name, attrs): + if name == 'foobar': return # we require an outer wrapper, which we promptly ignore. self.open_elements.append({'name':name, 'attrs':attrs.copy(), 'start':len(self.content), }) def endElement(self, name): - if name=='foobar': return # we require an outer wrapper, which we promptly ignore. + if name == 'foobar': return # we require an outer wrapper, which we promptly ignore. for i in range(len(self.open_elements)): e = self.open_elements[i] - if e['name']==name: + if e['name'] == name: # append a (start,end), name, attrs self.elements.append(((e['start'], #start position - len(self.content)),# current (end) position - e['name'],e['attrs']) + len(self.content)), # current (end) position + e['name'], e['attrs']) ) del self.open_elements[i] return @@ -39,14 +39,14 @@ class simpleHandler(xml.sax.ContentHandler): class MarkupString(unicode): """A simple class for dealing with marked up strings. When we are sliced, we return valid marked up strings, preserving markup.""" - def __init__(self, string): - unicode.__init__(self, string) + def __init__(self, string): + unicode.__init__(self) self.handler = simpleHandler() xml.sax.parseString((u"%s" % string).encode('utf-8'), self.handler) self.raw = self.handler.content def __getitem__(self, n): - return self.__getslice__(n,n+1) + return self.__getslice__(n, n + 1) def __getslice__(self, s, e): # only include relevant elements @@ -64,21 +64,21 @@ class MarkupString(unicode): name = el[1] attrs = el[2] # write our start tag - stag = "<%s"%name - for k,v in attrs.items(): stag += " %s=%s"%(k,xml.sax.saxutils.quoteattr(v)) + stag = "<%s" % name + for k, v in attrs.items(): stag += " %s=%s" % (k, xml.sax.saxutils.quoteattr(v)) stag += ">" - etag = ""%name # simple end tag + etag = "" % name # simple end tag spos = pos[0] epos = pos[1] - if spos < s: spos=s - if epos > e: epos=e + if spos < s: spos = s + if epos > e: epos = e if epos != spos: # we don't care about tags that don't markup any text - if not starts.has_key(spos): starts[spos]=[] + if not starts.has_key(spos): starts[spos] = [] starts[spos].append(stag) - if not ends.has_key(epos): ends[epos]=[] + if not ends.has_key(epos): ends[epos] = [] ends[epos].append(etag) outbuf = "" # our actual output string - for pos in range(s,e): # we move through positions + for pos in range(s, e): # we move through positions char = self.raw[pos] if ends.has_key(pos): # if there are endtags to insert... for et in ends[pos]: outbuf += et @@ -89,7 +89,7 @@ class MarkupString(unicode): for st in mystarts: outbuf += st outbuf += char if ends.has_key(e): - for et in ends[e]: outbuf+= et + for et in ends[e]: outbuf += et return MarkupString(outbuf) def __len__(self): diff --git a/lib/slughifi.py b/lib/slughifi.py index e7fa2212d..fe5c9e3ec 100644 --- a/lib/slughifi.py +++ b/lib/slughifi.py @@ -5,7 +5,7 @@ from types import UnicodeType from django.template.defaultfilters import slugify # default unicode character mapping ( you may not see some chars, leave as is ) -char_map = {u'À': 'A', u'Á': 'A', u'Â': 'A', u'Ã': 'A', u'Ä': 'Ae', u'Å': 'A', u'Æ': 'A', u'Ā': 'A', u'Ą': 'A', u'Ă': 'A', u'Ç': 'C', u'Ć': 'C', u'Č': 'C', u'Ĉ': 'C', u'Ċ': 'C', u'Ď': 'D', u'Đ': 'D', u'È': 'E', u'É': 'E', u'Ê': 'E', u'Ë': 'E', u'Ē': 'E', u'Ę': 'E', u'Ě': 'E', u'Ĕ': 'E', u'Ė': 'E', u'Ĝ': 'G', u'Ğ': 'G', u'Ġ': 'G', u'Ģ': 'G', u'Ĥ': 'H', u'Ħ': 'H', u'Ì': 'I', u'Í': 'I', u'Î': 'I', u'Ï': 'I', u'Ī': 'I', u'Ĩ': 'I', u'Ĭ': 'I', u'Į': 'I', u'İ': 'I', u'IJ': 'IJ', u'Ĵ': 'J', u'Ķ': 'K', u'Ľ': 'K', u'Ĺ': 'K', u'Ļ': 'K', u'Ŀ': 'K', u'Ł': 'L', u'Ñ': 'N', u'Ń': 'N', u'Ň': 'N', u'Ņ': 'N', u'Ŋ': 'N', u'Ò': 'O', u'Ó': 'O', u'Ô': 'O', u'Õ': 'O', u'Ö': 'Oe', u'Ø': 'O', u'Ō': 'O', u'Ő': 'O', u'Ŏ': 'O', u'Œ': 'OE', u'Ŕ': 'R', u'Ř': 'R', u'Ŗ': 'R', u'Ś': 'S', u'Ş': 'S', u'Ŝ': 'S', u'Ș': 'S', u'Š': 'S', u'Ť': 'T', u'Ţ': 'T', u'Ŧ': 'T', u'Ț': 'T', u'Ù': 'U', u'Ú': 'U', u'Û': 'U', u'Ü': 'Ue', u'Ū': 'U', u'Ů': 'U', u'Ű': 'U', u'Ŭ': 'U', u'Ũ': 'U', u'Ų': 'U', u'Ŵ': 'W', u'Ŷ': 'Y', u'Ÿ': 'Y', u'Ý': 'Y', u'Ź': 'Z', u'Ż': 'Z', u'Ž': 'Z', u'à': 'a', u'á': 'a', u'â': 'a', u'ã': 'a', u'ä': 'ae', u'ā': 'a', u'ą': 'a', u'ă': 'a', u'å': 'a', u'æ': 'ae', u'ç': 'c', u'ć': 'c', u'č': 'c', u'ĉ': 'c', u'ċ': 'c', u'ď': 'd', u'đ': 'd', u'è': 'e', u'é': 'e', u'ê': 'e', u'ë': 'e', u'ē': 'e', u'ę': 'e', u'ě': 'e', u'ĕ': 'e', u'ė': 'e', u'ƒ': 'f', u'ĝ': 'g', u'ğ': 'g', u'ġ': 'g', u'ģ': 'g', u'ĥ': 'h', u'ħ': 'h', u'ì': 'i', u'í': 'i', u'î': 'i', u'ï': 'i', u'ī': 'i', u'ĩ': 'i', u'ĭ': 'i', u'į': 'i', u'ı': 'i', u'ij': 'ij', u'ĵ': 'j', u'ķ': 'k', u'ĸ': 'k', u'ł': 'l', u'ľ': 'l', u'ĺ': 'l', u'ļ': 'l', u'ŀ': 'l', u'ñ': 'n', u'ń': 'n', u'ň': 'n', u'ņ': 'n', u'ʼn': 'n', u'ŋ': 'n', u'ò': 'o', u'ó': 'o', u'ô': 'o', u'õ': 'o', u'ö': 'oe', u'ø': 'o', u'ō': 'o', u'ő': 'o', u'ŏ': 'o', u'œ': 'oe', u'ŕ': 'r', u'ř': 'r', u'ŗ': 'r', u'ś': 's', u'š': 's', u'ť': 't', u'ù': 'u', u'ú': 'u', u'û': 'u', u'ü': 'ue', u'ū': 'u', u'ů': 'u', u'ű': 'u', u'ŭ': 'u', u'ũ': 'u', u'ų': 'u', u'ŵ': 'w', u'ÿ': 'y', u'ý': 'y', u'ŷ': 'y', u'ż': 'z', u'ź': 'z', u'ž': 'z', u'ß': 'ss', u'ſ': 'ss', u'Α': 'A', u'Ά': 'A', u'Ἀ': 'A', u'Ἁ': 'A', u'Ἂ': 'A', u'Ἃ': 'A', u'Ἄ': 'A', u'Ἅ': 'A', u'Ἆ': 'A', u'Ἇ': 'A', u'ᾈ': 'A', u'ᾉ': 'A', u'ᾊ': 'A', u'ᾋ': 'A', u'ᾌ': 'A', u'ᾍ': 'A', u'ᾎ': 'A', u'ᾏ': 'A', u'Ᾰ': 'A', u'Ᾱ': 'A', u'Ὰ': 'A', u'Ά': 'A', u'ᾼ': 'A', u'Β': 'B', u'Γ': 'G', u'Δ': 'D', u'Ε': 'E', u'Έ': 'E', u'Ἐ': 'E', u'Ἑ': 'E', u'Ἒ': 'E', u'Ἓ': 'E', u'Ἔ': 'E', u'Ἕ': 'E', u'Έ': 'E', u'Ὲ': 'E', u'Ζ': 'Z', u'Η': 'I', u'Ή': 'I', u'Ἠ': 'I', u'Ἡ': 'I', u'Ἢ': 'I', u'Ἣ': 'I', u'Ἤ': 'I', u'Ἥ': 'I', u'Ἦ': 'I', u'Ἧ': 'I', u'ᾘ': 'I', u'ᾙ': 'I', u'ᾚ': 'I', u'ᾛ': 'I', u'ᾜ': 'I', u'ᾝ': 'I', u'ᾞ': 'I', u'ᾟ': 'I', u'Ὴ': 'I', u'Ή': 'I', u'ῌ': 'I', u'Θ': 'TH', u'Ι': 'I', u'Ί': 'I', u'Ϊ': 'I', u'Ἰ': 'I', u'Ἱ': 'I', u'Ἲ': 'I', u'Ἳ': 'I', u'Ἴ': 'I', u'Ἵ': 'I', u'Ἶ': 'I', u'Ἷ': 'I', u'Ῐ': 'I', u'Ῑ': 'I', u'Ὶ': 'I', u'Ί': 'I', u'Κ': 'K', u'Λ': 'L', u'Μ': 'M', u'Ν': 'N', u'Ξ': 'KS', u'Ο': 'O', u'Ό': 'O', u'Ὀ': 'O', u'Ὁ': 'O', u'Ὂ': 'O', u'Ὃ': 'O', u'Ὄ': 'O', u'Ὅ': 'O', u'Ὸ': 'O', u'Ό': 'O', u'Π': 'P', u'Ρ': 'R', u'Ῥ': 'R', u'Σ': 'S', u'Τ': 'T', u'Υ': 'Y', u'Ύ': 'Y', u'Ϋ': 'Y', u'Ὑ': 'Y', u'Ὓ': 'Y', u'Ὕ': 'Y', u'Ὗ': 'Y', u'Ῠ': 'Y', u'Ῡ': 'Y', u'Ὺ': 'Y', u'Ύ': 'Y', u'Φ': 'F', u'Χ': 'X', u'Ψ': 'PS', u'Ω': 'O', u'Ώ': 'O', u'Ὠ': 'O', u'Ὡ': 'O', u'Ὢ': 'O', u'Ὣ': 'O', u'Ὤ': 'O', u'Ὥ': 'O', u'Ὦ': 'O', u'Ὧ': 'O', u'ᾨ': 'O', u'ᾩ': 'O', u'ᾪ': 'O', u'ᾫ': 'O', u'ᾬ': 'O', u'ᾭ': 'O', u'ᾮ': 'O', u'ᾯ': 'O', u'Ὼ': 'O', u'Ώ': 'O', u'ῼ': 'O', u'α': 'a', u'ά': 'a', u'ἀ': 'a', u'ἁ': 'a', u'ἂ': 'a', u'ἃ': 'a', u'ἄ': 'a', u'ἅ': 'a', u'ἆ': 'a', u'ἇ': 'a', u'ᾀ': 'a', u'ᾁ': 'a', u'ᾂ': 'a', u'ᾃ': 'a', u'ᾄ': 'a', u'ᾅ': 'a', u'ᾆ': 'a', u'ᾇ': 'a', u'ὰ': 'a', u'ά': 'a', u'ᾰ': 'a', u'ᾱ': 'a', u'ᾲ': 'a', u'ᾳ': 'a', u'ᾴ': 'a', u'ᾶ': 'a', u'ᾷ': 'a', u'β': 'b', u'γ': 'g', u'δ': 'd', u'ε': 'e', u'έ': 'e', u'ἐ': 'e', u'ἑ': 'e', u'ἒ': 'e', u'ἓ': 'e', u'ἔ': 'e', u'ἕ': 'e', u'ὲ': 'e', u'έ': 'e', u'ζ': 'z', u'η': 'i', u'ή': 'i', u'ἠ': 'i', u'ἡ': 'i', u'ἢ': 'i', u'ἣ': 'i', u'ἤ': 'i', u'ἥ': 'i', u'ἦ': 'i', u'ἧ': 'i', u'ᾐ': 'i', u'ᾑ': 'i', u'ᾒ': 'i', u'ᾓ': 'i', u'ᾔ': 'i', u'ᾕ': 'i', u'ᾖ': 'i', u'ᾗ': 'i', u'ὴ': 'i', u'ή': 'i', u'ῂ': 'i', u'ῃ': 'i', u'ῄ': 'i', u'ῆ': 'i', u'ῇ': 'i', u'θ': 'th', u'ι': 'i', u'ί': 'i', u'ϊ': 'i', u'ΐ': 'i', u'ἰ': 'i', u'ἱ': 'i', u'ἲ': 'i', u'ἳ': 'i', u'ἴ': 'i', u'ἵ': 'i', u'ἶ': 'i', u'ἷ': 'i', u'ὶ': 'i', u'ί': 'i', u'ῐ': 'i', u'ῑ': 'i', u'ῒ': 'i', u'ΐ': 'i', u'ῖ': 'i', u'ῗ': 'i', u'κ': 'k', u'λ': 'l', u'μ': 'm', u'ν': 'n', u'ξ': 'ks', u'ο': 'o', u'ό': 'o', u'ὀ': 'o', u'ὁ': 'o', u'ὂ': 'o', u'ὃ': 'o', u'ὄ': 'o', u'ὅ': 'o', u'ὸ': 'o', u'ό': 'o', u'π': 'p', u'ρ': 'r', u'ῤ': 'r', u'ῥ': 'r', u'σ': 's', u'ς': 's', u'τ': 't', u'υ': 'y', u'ύ': 'y', u'ϋ': 'y', u'ΰ': 'y', u'ὐ': 'y', u'ὑ': 'y', u'ὒ': 'y', u'ὓ': 'y', u'ὔ': 'y', u'ὕ': 'y', u'ὖ': 'y', u'ὗ': 'y', u'ὺ': 'y', u'ύ': 'y', u'ῠ': 'y', u'ῡ': 'y', u'ῢ': 'y', u'ΰ': 'y', u'ῦ': 'y', u'ῧ': 'y', u'φ': 'f', u'χ': 'x', u'ψ': 'ps', u'ω': 'o', u'ώ': 'o', u'ὠ': 'o', u'ὡ': 'o', u'ὢ': 'o', u'ὣ': 'o', u'ὤ': 'o', u'ὥ': 'o', u'ὦ': 'o', u'ὧ': 'o', u'ᾠ': 'o', u'ᾡ': 'o', u'ᾢ': 'o', u'ᾣ': 'o', u'ᾤ': 'o', u'ᾥ': 'o', u'ᾦ': 'o', u'ᾧ': 'o', u'ὼ': 'o', u'ώ': 'o', u'ῲ': 'o', u'ῳ': 'o', u'ῴ': 'o', u'ῶ': 'o', u'ῷ': 'o', u'¨': '', u'΅': '', u'᾿': '', u'῾': '', u'῍': '', u'῝': '', u'῎': '', u'῞': '', u'῏': '', u'῟': '', u'῀': '', u'῁': '', u'΄': '', u'΅': '', u'`': '', u'῭': '', u'ͺ': '', u'᾽': '', u'А': 'A', u'Б': 'B', u'В': 'V', u'Г': 'G', u'Д': 'D', u'Е': 'E', u'Ё': 'E', u'Ж': 'ZH', u'З': 'Z', u'И': 'I', u'Й': 'I', u'К': 'K', u'Л': 'L', u'М': 'M', u'Н': 'N', u'О': 'O', u'П': 'P', u'Р': 'R', u'С': 'S', u'Т': 'T', u'У': 'U', u'Ф': 'F', u'Х': 'KH', u'Ц': 'TS', u'Ч': 'CH', u'Ш': 'SH', u'Щ': 'SHCH', u'Ы': 'Y', u'Э': 'E', u'Ю': 'YU', u'Я': 'YA', u'а': 'A', u'б': 'B', u'в': 'V', u'г': 'G', u'д': 'D', u'е': 'E', u'ё': 'E', u'ж': 'ZH', u'з': 'Z', u'и': 'I', u'й': 'I', u'к': 'K', u'л': 'L', u'м': 'M', u'н': 'N', u'о': 'O', u'п': 'P', u'р': 'R', u'с': 'S', u'т': 'T', u'у': 'U', u'ф': 'F', u'х': 'KH', u'ц': 'TS', u'ч': 'CH', u'ш': 'SH', u'щ': 'SHCH', u'ы': 'Y', u'э': 'E', u'ю': 'YU', u'я': 'YA', u'Ъ': '', u'ъ': '', u'Ь': '', u'ь': '', u'ð': 'd', u'Ð': 'D', u'þ': 'th', u'Þ': 'TH', +char_map = {u'À': 'A', u'Á': 'A', u'Â': 'A', u'Ã': 'A', u'Ä': 'Ae', u'Å': 'A', u'Æ': 'A', u'Ā': 'A', u'Ą': 'A', u'Ă': 'A', u'Ç': 'C', u'Ć': 'C', u'Č': 'C', u'Ĉ': 'C', u'Ċ': 'C', u'Ď': 'D', u'Đ': 'D', u'È': 'E', u'É': 'E', u'Ê': 'E', u'Ë': 'E', u'Ē': 'E', u'Ę': 'E', u'Ě': 'E', u'Ĕ': 'E', u'Ė': 'E', u'Ĝ': 'G', u'Ğ': 'G', u'Ġ': 'G', u'Ģ': 'G', u'Ĥ': 'H', u'Ħ': 'H', u'Ì': 'I', u'Í': 'I', u'Î': 'I', u'Ï': 'I', u'Ī': 'I', u'Ĩ': 'I', u'Ĭ': 'I', u'Į': 'I', u'İ': 'I', u'IJ': 'IJ', u'Ĵ': 'J', u'Ķ': 'K', u'Ľ': 'K', u'Ĺ': 'K', u'Ļ': 'K', u'Ŀ': 'K', u'Ł': 'L', u'Ñ': 'N', u'Ń': 'N', u'Ň': 'N', u'Ņ': 'N', u'Ŋ': 'N', u'Ò': 'O', u'Ó': 'O', u'Ô': 'O', u'Õ': 'O', u'Ö': 'Oe', u'Ø': 'O', u'Ō': 'O', u'Ő': 'O', u'Ŏ': 'O', u'Œ': 'OE', u'Ŕ': 'R', u'Ř': 'R', u'Ŗ': 'R', u'Ś': 'S', u'Ş': 'S', u'Ŝ': 'S', u'Ș': 'S', u'Š': 'S', u'Ť': 'T', u'Ţ': 'T', u'Ŧ': 'T', u'Ț': 'T', u'Ù': 'U', u'Ú': 'U', u'Û': 'U', u'Ü': 'Ue', u'Ū': 'U', u'Ů': 'U', u'Ű': 'U', u'Ŭ': 'U', u'Ũ': 'U', u'Ų': 'U', u'Ŵ': 'W', u'Ŷ': 'Y', u'Ÿ': 'Y', u'Ý': 'Y', u'Ź': 'Z', u'Ż': 'Z', u'Ž': 'Z', u'à': 'a', u'á': 'a', u'â': 'a', u'ã': 'a', u'ä': 'ae', u'ā': 'a', u'ą': 'a', u'ă': 'a', u'å': 'a', u'æ': 'ae', u'ç': 'c', u'ć': 'c', u'č': 'c', u'ĉ': 'c', u'ċ': 'c', u'ď': 'd', u'đ': 'd', u'è': 'e', u'é': 'e', u'ê': 'e', u'ë': 'e', u'ē': 'e', u'ę': 'e', u'ě': 'e', u'ĕ': 'e', u'ė': 'e', u'ƒ': 'f', u'ĝ': 'g', u'ğ': 'g', u'ġ': 'g', u'ģ': 'g', u'ĥ': 'h', u'ħ': 'h', u'ì': 'i', u'í': 'i', u'î': 'i', u'ï': 'i', u'ī': 'i', u'ĩ': 'i', u'ĭ': 'i', u'į': 'i', u'ı': 'i', u'ij': 'ij', u'ĵ': 'j', u'ķ': 'k', u'ĸ': 'k', u'ł': 'l', u'ľ': 'l', u'ĺ': 'l', u'ļ': 'l', u'ŀ': 'l', u'ñ': 'n', u'ń': 'n', u'ň': 'n', u'ņ': 'n', u'ʼn': 'n', u'ŋ': 'n', u'ò': 'o', u'ó': 'o', u'ô': 'o', u'õ': 'o', u'ö': 'oe', u'ø': 'o', u'ō': 'o', u'ő': 'o', u'ŏ': 'o', u'œ': 'oe', u'ŕ': 'r', u'ř': 'r', u'ŗ': 'r', u'ś': 's', u'š': 's', u'ť': 't', u'ù': 'u', u'ú': 'u', u'û': 'u', u'ü': 'ue', u'ū': 'u', u'ů': 'u', u'ű': 'u', u'ŭ': 'u', u'ũ': 'u', u'ų': 'u', u'ŵ': 'w', u'ÿ': 'y', u'ý': 'y', u'ŷ': 'y', u'ż': 'z', u'ź': 'z', u'ž': 'z', u'ß': 'ss', u'ſ': 'ss', u'Α': 'A', u'Ά': 'A', u'Ἀ': 'A', u'Ἁ': 'A', u'Ἂ': 'A', u'Ἃ': 'A', u'Ἄ': 'A', u'Ἅ': 'A', u'Ἆ': 'A', u'Ἇ': 'A', u'ᾈ': 'A', u'ᾉ': 'A', u'ᾊ': 'A', u'ᾋ': 'A', u'ᾌ': 'A', u'ᾍ': 'A', u'ᾎ': 'A', u'ᾏ': 'A', u'Ᾰ': 'A', u'Ᾱ': 'A', u'Ὰ': 'A', u'Ά': 'A', u'ᾼ': 'A', u'Β': 'B', u'Γ': 'G', u'Δ': 'D', u'Ε': 'E', u'Έ': 'E', u'Ἐ': 'E', u'Ἑ': 'E', u'Ἒ': 'E', u'Ἓ': 'E', u'Ἔ': 'E', u'Ἕ': 'E', u'Έ': 'E', u'Ὲ': 'E', u'Ζ': 'Z', u'Η': 'I', u'Ή': 'I', u'Ἠ': 'I', u'Ἡ': 'I', u'Ἢ': 'I', u'Ἣ': 'I', u'Ἤ': 'I', u'Ἥ': 'I', u'Ἦ': 'I', u'Ἧ': 'I', u'ᾘ': 'I', u'ᾙ': 'I', u'ᾚ': 'I', u'ᾛ': 'I', u'ᾜ': 'I', u'ᾝ': 'I', u'ᾞ': 'I', u'ᾟ': 'I', u'Ὴ': 'I', u'Ή': 'I', u'ῌ': 'I', u'Θ': 'TH', u'Ι': 'I', u'Ί': 'I', u'Ϊ': 'I', u'Ἰ': 'I', u'Ἱ': 'I', u'Ἲ': 'I', u'Ἳ': 'I', u'Ἴ': 'I', u'Ἵ': 'I', u'Ἶ': 'I', u'Ἷ': 'I', u'Ῐ': 'I', u'Ῑ': 'I', u'Ὶ': 'I', u'Ί': 'I', u'Κ': 'K', u'Λ': 'L', u'Μ': 'M', u'Ν': 'N', u'Ξ': 'KS', u'Ο': 'O', u'Ό': 'O', u'Ὀ': 'O', u'Ὁ': 'O', u'Ὂ': 'O', u'Ὃ': 'O', u'Ὄ': 'O', u'Ὅ': 'O', u'Ὸ': 'O', u'Ό': 'O', u'Π': 'P', u'Ρ': 'R', u'Ῥ': 'R', u'Σ': 'S', u'Τ': 'T', u'Υ': 'Y', u'Ύ': 'Y', u'Ϋ': 'Y', u'Ὑ': 'Y', u'Ὓ': 'Y', u'Ὕ': 'Y', u'Ὗ': 'Y', u'Ῠ': 'Y', u'Ῡ': 'Y', u'Ὺ': 'Y', u'Ύ': 'Y', u'Φ': 'F', u'Χ': 'X', u'Ψ': 'PS', u'Ω': 'O', u'Ώ': 'O', u'Ὠ': 'O', u'Ὡ': 'O', u'Ὢ': 'O', u'Ὣ': 'O', u'Ὤ': 'O', u'Ὥ': 'O', u'Ὦ': 'O', u'Ὧ': 'O', u'ᾨ': 'O', u'ᾩ': 'O', u'ᾪ': 'O', u'ᾫ': 'O', u'ᾬ': 'O', u'ᾭ': 'O', u'ᾮ': 'O', u'ᾯ': 'O', u'Ὼ': 'O', u'Ώ': 'O', u'ῼ': 'O', u'α': 'a', u'ά': 'a', u'ἀ': 'a', u'ἁ': 'a', u'ἂ': 'a', u'ἃ': 'a', u'ἄ': 'a', u'ἅ': 'a', u'ἆ': 'a', u'ἇ': 'a', u'ᾀ': 'a', u'ᾁ': 'a', u'ᾂ': 'a', u'ᾃ': 'a', u'ᾄ': 'a', u'ᾅ': 'a', u'ᾆ': 'a', u'ᾇ': 'a', u'ὰ': 'a', u'ά': 'a', u'ᾰ': 'a', u'ᾱ': 'a', u'ᾲ': 'a', u'ᾳ': 'a', u'ᾴ': 'a', u'ᾶ': 'a', u'ᾷ': 'a', u'β': 'b', u'γ': 'g', u'δ': 'd', u'ε': 'e', u'έ': 'e', u'ἐ': 'e', u'ἑ': 'e', u'ἒ': 'e', u'ἓ': 'e', u'ἔ': 'e', u'ἕ': 'e', u'ὲ': 'e', u'έ': 'e', u'ζ': 'z', u'η': 'i', u'ή': 'i', u'ἠ': 'i', u'ἡ': 'i', u'ἢ': 'i', u'ἣ': 'i', u'ἤ': 'i', u'ἥ': 'i', u'ἦ': 'i', u'ἧ': 'i', u'ᾐ': 'i', u'ᾑ': 'i', u'ᾒ': 'i', u'ᾓ': 'i', u'ᾔ': 'i', u'ᾕ': 'i', u'ᾖ': 'i', u'ᾗ': 'i', u'ὴ': 'i', u'ή': 'i', u'ῂ': 'i', u'ῃ': 'i', u'ῄ': 'i', u'ῆ': 'i', u'ῇ': 'i', u'θ': 'th', u'ι': 'i', u'ί': 'i', u'ϊ': 'i', u'ΐ': 'i', u'ἰ': 'i', u'ἱ': 'i', u'ἲ': 'i', u'ἳ': 'i', u'ἴ': 'i', u'ἵ': 'i', u'ἶ': 'i', u'ἷ': 'i', u'ὶ': 'i', u'ί': 'i', u'ῐ': 'i', u'ῑ': 'i', u'ῒ': 'i', u'ΐ': 'i', u'ῖ': 'i', u'ῗ': 'i', u'κ': 'k', u'λ': 'l', u'μ': 'm', u'ν': 'n', u'ξ': 'ks', u'ο': 'o', u'ό': 'o', u'ὀ': 'o', u'ὁ': 'o', u'ὂ': 'o', u'ὃ': 'o', u'ὄ': 'o', u'ὅ': 'o', u'ὸ': 'o', u'ό': 'o', u'π': 'p', u'ρ': 'r', u'ῤ': 'r', u'ῥ': 'r', u'σ': 's', u'ς': 's', u'τ': 't', u'υ': 'y', u'ύ': 'y', u'ϋ': 'y', u'ΰ': 'y', u'ὐ': 'y', u'ὑ': 'y', u'ὒ': 'y', u'ὓ': 'y', u'ὔ': 'y', u'ὕ': 'y', u'ὖ': 'y', u'ὗ': 'y', u'ὺ': 'y', u'ύ': 'y', u'ῠ': 'y', u'ῡ': 'y', u'ῢ': 'y', u'ΰ': 'y', u'ῦ': 'y', u'ῧ': 'y', u'φ': 'f', u'χ': 'x', u'ψ': 'ps', u'ω': 'o', u'ώ': 'o', u'ὠ': 'o', u'ὡ': 'o', u'ὢ': 'o', u'ὣ': 'o', u'ὤ': 'o', u'ὥ': 'o', u'ὦ': 'o', u'ὧ': 'o', u'ᾠ': 'o', u'ᾡ': 'o', u'ᾢ': 'o', u'ᾣ': 'o', u'ᾤ': 'o', u'ᾥ': 'o', u'ᾦ': 'o', u'ᾧ': 'o', u'ὼ': 'o', u'ώ': 'o', u'ῲ': 'o', u'ῳ': 'o', u'ῴ': 'o', u'ῶ': 'o', u'ῷ': 'o', u'¨': '', u'΅': '', u'᾿': '', u'῾': '', u'῍': '', u'῝': '', u'῎': '', u'῞': '', u'῏': '', u'῟': '', u'῀': '', u'῁': '', u'΄': '', u'΅': '', u'`': '', u'῭': '', u'ͺ': '', u'᾽': '', u'А': 'A', u'Б': 'B', u'В': 'V', u'Г': 'G', u'Д': 'D', u'Е': 'E', u'Ё': 'E', u'Ж': 'ZH', u'З': 'Z', u'И': 'I', u'Й': 'I', u'К': 'K', u'Л': 'L', u'М': 'M', u'Н': 'N', u'О': 'O', u'П': 'P', u'Р': 'R', u'С': 'S', u'Т': 'T', u'У': 'U', u'Ф': 'F', u'Х': 'KH', u'Ц': 'TS', u'Ч': 'CH', u'Ш': 'SH', u'Щ': 'SHCH', u'Ы': 'Y', u'Э': 'E', u'Ю': 'YU', u'Я': 'YA', u'а': 'A', u'б': 'B', u'в': 'V', u'г': 'G', u'д': 'D', u'е': 'E', u'ё': 'E', u'ж': 'ZH', u'з': 'Z', u'и': 'I', u'й': 'I', u'к': 'K', u'л': 'L', u'м': 'M', u'н': 'N', u'о': 'O', u'п': 'P', u'р': 'R', u'с': 'S', u'т': 'T', u'у': 'U', u'ф': 'F', u'х': 'KH', u'ц': 'TS', u'ч': 'CH', u'ш': 'SH', u'щ': 'SHCH', u'ы': 'Y', u'э': 'E', u'ю': 'YU', u'я': 'YA', u'Ъ': '', u'ъ': '', u'Ь': '', u'ь': '', u'ð': 'd', u'Ð': 'D', u'þ': 'th', u'Þ': 'TH', u'ა': 'a', u'ბ': 'b', u'გ': 'g', u'დ': 'd', u'ე': 'e', u'ვ': 'v', u'ზ': 'z', u'თ': 't', u'ი': 'i', u'კ': 'k', u'ლ': 'l', u'მ': 'm', u'ნ': 'n', u'ო': 'o', u'პ': 'p', u'ჟ': 'zh', u'რ': 'r', u'ს': 's', u'ტ': 't', u'უ': 'u', u'ფ': 'p', u'ქ': 'k', u'ღ': 'gh', u'ყ': 'q', u'შ': 'sh', u'ჩ': 'ch', u'ც': 'ts', u'ძ': 'dz', u'წ': 'ts', u'ჭ': 'ch', u'ხ': 'kh', u'ჯ': 'j', u'ჰ': 'h' } def replace_char(m): @@ -14,43 +14,43 @@ def replace_char(m): return char_map[char] else: return char - + def slughifi(value, do_slugify=True, overwrite_char_map={}): """ High Fidelity slugify - slughifi.py, v 0.1 - + Examples : - + >>> text = 'C\'est déjà l\'été.' - + >>> slughifi(text) 'cest-deja-lete' - + >>> slughifi(text, overwrite_char_map={u'\'': '-',}) 'c-est-deja-l-ete' - + >>> slughifi(text, do_slugify=False) "C'est deja l'ete." - + # Normal slugify removes accented characters >>> slugify(text) 'cest-dj-lt' - + """ # unicodification if type(value) != UnicodeType: value = unicode(value, 'utf-8', 'ignore') - + # overwrite chararcter mapping char_map.update(overwrite_char_map) # try to replace chars value = re.sub('[^a-zA-Z0-9\\s\\-]{1}', replace_char, value) - + # apply django default slugify if do_slugify: value = slugify(value) - + return value.encode('ascii', 'ignore') diff --git a/requirements.txt b/requirements.txt index a577d714d..62485f381 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,6 @@ sorl-thumbnail>=3.2 # home-brewed & dependencies lxml>=2.2.2 --e git+git://github.com/fnp/librarian.git@d43d87400dcc19851442#egg=librarian +# -e git+git://github.com/fnp/librarian.git@d43d87400dcc19851442#egg=librarian # MySQL-python>=1.2,<2.0 diff --git a/scripts/conv_genre_families.py b/scripts/conv_genre_families.py index 247fc2c60..f798d69eb 100644 --- a/scripts/conv_genre_families.py +++ b/scripts/conv_genre_families.py @@ -17,13 +17,13 @@ doc = etree.parse('rodziny.xml') for element in doc.findall('//span'): themes = [s.strip() for s in element.text.split(',')] - + element.text = u'' - + for theme in themes: try: Tag.objects.get(slug=slughifi(theme)) - + link = etree.SubElement(element, 'a', href=u'/katalog/%s' % slughifi(theme)) link.text = theme link.tail = ', ' diff --git a/scripts/irename.py b/scripts/irename.py index 9cc721b43..3268f45cb 100755 --- a/scripts/irename.py +++ b/scripts/irename.py @@ -25,19 +25,19 @@ for file_name in os.listdir('mp3'): base_name, ext = splitext(file_name) if ext != '.mp3': continue - + audio = easyid3.EasyID3(join('mp3', file_name)) title = audio['title'][0] artist = title.split(',', 1)[0].strip() artist_slug = slughifi(artist) title_part = slughifi(title.rsplit(',', 1)[1].strip()) - + print "--------------------" print "File: %s" % file_name print "Title: %s" % title print print "Matching books:" - + matching_books = [book for book in Book.tagged.with_all(artist_slug) if book.slug not in chosen_book_slugs] matching_books = [book for book in matching_books if title_part in book.slug] @@ -51,11 +51,10 @@ for file_name in os.listdir('mp3'): else: print "Skipping %s: No matching book found" % file_name continue - + print "You chose %d (%s)" % (i, matching_books[i].slug) - + chosen_book_slugs.add(matching_books[i].slug) os.rename(join('mp3', file_name), join('new_mp3', matching_books[i].slug + '.mp3')) os.rename(join('oggvorbis', base_name + '.ogg'), join('new_ogg', matching_books[i].slug + '.ogg')) - - \ No newline at end of file + diff --git a/scripts/setmainpage.py b/scripts/setmainpage.py index e83a07f27..324424356 100755 --- a/scripts/setmainpage.py +++ b/scripts/setmainpage.py @@ -31,7 +31,7 @@ for tag in Tag.objects.all(): tag.main_page = True else: tag.main_page = False - + tag.save() sys.stderr.write('.') diff --git a/wolnelektury/manage.py b/wolnelektury/manage.py index a9acc04a7..93b280527 100755 --- a/wolnelektury/manage.py +++ b/wolnelektury/manage.py @@ -1,12 +1,18 @@ #!/usr/bin/env python -from os.path import abspath, dirname, join +import os.path import sys +ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + # Add apps and lib directories to PYTHONPATH -sys.path.insert(0, abspath(join(dirname(__file__), '../apps'))) -sys.path.insert(0, abspath(join(dirname(__file__), '../lib'))) +sys.path = [ + os.path.join(ROOT, 'apps'), + os.path.join(ROOT, 'lib'), + os.path.join(ROOT, 'lib/librarian'), +] + sys.path from django.core.management import execute_manager + try: import settings # Assumed to be in the same directory. except ImportError: diff --git a/wolnelektury/middleware.py b/wolnelektury/middleware.py index c9a17dc3a..60b382cd3 100644 --- a/wolnelektury/middleware.py +++ b/wolnelektury/middleware.py @@ -118,7 +118,7 @@ class ProfileMiddleware(object): response.content += self.summary_for_files(stats_str) os.unlink(self.tmpfile) - + response.content += '\n%d SQL Queries:\n' % len(connection.queries) response.content += pprint.pformat(connection.queries) diff --git a/wolnelektury/settings.py b/wolnelektury/settings.py index b7c55fdab..2ece0b1ac 100644 --- a/wolnelektury/settings.py +++ b/wolnelektury/settings.py @@ -153,8 +153,8 @@ COMPRESS_JS = { 'output_filename': 'js/jquery.min.js', }, 'all': { - 'source_filenames': ('js/jquery.autocomplete.js', 'js/jquery.form.js', - 'js/jquery.countdown.js', 'js/jquery.countdown-pl.js', + 'source_filenames': ('js/jquery.autocomplete.js', 'js/jquery.form.js', + 'js/jquery.countdown.js', 'js/jquery.countdown-pl.js', 'js/jquery.countdown-de.js', 'js/jquery.countdown-uk.js', 'js/jquery.countdown-es.js', 'js/jquery.countdown-lt.js', 'js/jquery.countdown-ru.js', 'js/jquery.countdown-fr.js', diff --git a/wolnelektury/static/js/book.js b/wolnelektury/static/js/book.js index 41e3be28c..598ef2eb1 100644 --- a/wolnelektury/static/js/book.js +++ b/wolnelektury/static/js/book.js @@ -1,4 +1,4 @@ -$(function() { +$(function() { function scrollToAnchor(anchor) { if (anchor) { var element = $('a[name="' + anchor.slice(1) + '"]'); @@ -9,22 +9,22 @@ $(function() { } } } - + $.highlightFade.defaults.speed = 3000; $('#toc').hide(); if ($('#toc li').length == 0) { $('#menu li a[href="#toc"]').remove(); } - + // On page load, scroll to anchor scrollToAnchor(window.location.hash) - + $('#toc, #themes, #book-text').delegate('click', 'a', function(event) { event.preventDefault(); $('#menu li a.selected').click(); scrollToAnchor($(this).attr('href')); }); - + $('#menu li a').toggle(function() { $('#menu li a.selected').click(); $(this).addClass('selected'); diff --git a/wolnelektury/static/js/catalogue.js b/wolnelektury/static/js/catalogue.js index dc4c07dee..6e0f562fb 100644 --- a/wolnelektury/static/js/catalogue.js +++ b/wolnelektury/static/js/catalogue.js @@ -3,49 +3,49 @@ var LOCALE_TEXTS = { "DELETE_SHELF": "Czy na pewno usunąć półkę", "HIDE_DESCRIPTION": "Zwiń opis", "EXPAND_DESCRIPTION": "Rozwiń opis", - "LOADING": "Ładowanie" + "LOADING": "Ładowanie" }, "de": { "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" }, "fr": { "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" }, "en": { "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" - }, + "LOADING": "Translate me!" + }, "ru": { "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" }, "es": { "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" }, "lt":{ "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" }, "uk":{ "DELETE_SHELF": "Translate me!", "HIDE_DESCRIPTION": "Translate me!", "EXPAND_DESCRIPTION": "Translate me!", - "LOADING": "Translate me!" + "LOADING": "Translate me!" } } var BANNER_TEXTS = [ @@ -85,7 +85,7 @@ function changeBannerText() { $(this).html(BANNER_TEXTS[index]); $(this).fadeIn('slow'); }); - + setTimeout(changeBannerText, 30 * 1000); } } @@ -93,35 +93,35 @@ function changeBannerText() { function autocomplete_result_handler(event, item) { $(event.target).closest('form').submit(); } -function serverTime() { - var time = null; - $.ajax({url: '/katalog/zegar/', - async: false, dataType: 'text', - success: function(text) { +function serverTime() { + var time = null; + $.ajax({url: '/katalog/zegar/', + async: false, dataType: 'text', + success: function(text) { time = new Date(text); }, error: function(http, message, exc) { - time = new Date(); - }}); - return time; + time = new Date(); + }}); + return time; } (function($) { $(function() { - + $('form input').labelify({labelledClass: 'blur'}); - + target = $('#login-register-window div.target'); - + $('#show-registration-form').click(function() { $('#login-form').hide(); $('#registration-form').show(); }); - + $('#show-login-form').click(function() { $('#registration-form').hide(); $('#login-form').show(); }); - + // Fragments $('.fragment-text').each(function() { if ($(this).prev().filter('.fragment-short-text').length) { @@ -136,7 +136,7 @@ function serverTime() { }) } }); - + $('.fragment-short-text').click(function() { $(this).fadeOut(function() { $(this).next().fadeIn() }); return false; @@ -144,21 +144,21 @@ function serverTime() { function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); }, function() { $(this).css({background: '#FFF'}); } ); - + $('.show-all-tags').click(function() { - $(this).parent().parent().fadeOut(function() { + $(this).parent().parent().fadeOut(function() { $(this).next().fadeIn(); }); return false; }); - + $('.hide-all-tags').click(function() { $(this).parent().parent().fadeOut(function() { $(this).prev().fadeIn(); }); - return false; + return false; }); - + $('#registration-form').ajaxForm({ dataType: 'json', beforeSubmit: function() { @@ -179,7 +179,7 @@ function serverTime() { } } }); - + $('#login-form').ajaxForm({ dataType: 'json', beforeSubmit: function() { @@ -200,7 +200,7 @@ function serverTime() { } } }); - + $('#login-register-window').jqm({ target: target[0], overlay: 60, @@ -212,7 +212,7 @@ function serverTime() { hash.w.show(); } }); - + $('ul.shelf-list li').hover(function() { $(this).css({background: '#EEE', cursor: 'pointer'}); }, function() { @@ -220,8 +220,8 @@ function serverTime() { }).click(function() { location.href = $('a.visit-shelf', this).attr('href'); }); - - $('.delete-shelf').click(function() { + + $('.delete-shelf').click(function() { var link = $(this); var shelf_name = $('.visit-shelf', link.parent()).text(); if (confirm(LOCALE_TEXTS[LANGUAGE_CODE]['DELETE_SHELF']+ ' '+ shelf_name + '?')) { @@ -231,7 +231,7 @@ function serverTime() { } return false; }); - + $('#user-shelves-window').jqm({ ajax: '@href', target: $('#user-shelves-window div.target')[0], @@ -243,14 +243,14 @@ function serverTime() { $('div.header', hash.w).css({width: $(hash.t).width()}); hash.w.show(); }, - onLoad: function(hash) { + onLoad: function(hash) { $('form', hash.w).ajaxForm({ target: $('#user-shelves-window div.target'), success: function() { setTimeout(function() { $('#user-shelves-window').jqmHide() }, 1000) } }); - + $('input', hash.w).labelify({labelledClass: 'blur'}); - + $('ul.shelf-list li', hash.w).hover(function() { $(this).css({background: '#EEE', cursor: 'pointer'}); }, function() { @@ -258,7 +258,7 @@ function serverTime() { }).click(function() { location.href = $('a.visit-shelf', this).attr('href'); }); - + $('.delete-shelf').click(function() { var link = $(this); var shelf_name = $('.visit-shelf', link.parent()).text(); @@ -283,7 +283,7 @@ function serverTime() { $('div.header', hash.w).css({width: $(hash.t).width()}); hash.w.show(); }, - onLoad: function(hash) { + onLoad: function(hash) { $('form', hash.w).ajaxForm({ dataType: 'json', target: $('#suggest-window div.target'), @@ -304,14 +304,14 @@ function serverTime() { }); } }); - + $('#books-list .book').hover( function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); }, function() { $(this).css({background: '#FFF'}); } ).click(function() { location.href = $('h2 a', this).attr('href'); }); - + function toggled_by_slide(cont, short_el, long_el, button, short_text, long_text) { function toggle(cont, short_el, long_el, button, short_text, long_text) { if (cont.hasClass('short')) { @@ -343,8 +343,8 @@ function serverTime() { toggle(cont, short_el, long_el, button, short_text, long_text) }) } - toggled_by_slide($('#description'), $('#description-short'), $('#description-long'), - $('#toggle-description p'), + toggled_by_slide($('#description'), $('#description-short'), $('#description-long'), + $('#toggle-description p'), LOCALE_TEXTS[LANGUAGE_CODE]['EXPAND_DESCRIPTION']+' ▼', LOCALE_TEXTS[LANGUAGE_CODE]['HIDE_DESCRIPTION'] + ' ▲' ); @@ -361,17 +361,17 @@ function serverTime() { }); var target = $('#set-window div.target'); - + $('#set-window').jqm({ - ajax: '@href', + ajax: '@href', target: target[0], overlay: 60, - trigger: 'a.jqm-trigger', - onShow: function(hash) { + trigger: 'a.jqm-trigger', + onShow: function(hash) { var offset = $(hash.t).offset(); target.html('

    '+LOCALE_TEXTS[LANGUAGE_CODE]['DELETE_SHELF']+'

    '); hash.w.css({position: 'absolute', left: offset.left, top: offset.top}).show() }, - onLoad: function(hash) { + onLoad: function(hash) { try { $('#createShelfTrigger').click(function(){ $('#createNewShelf').show(); @@ -380,14 +380,14 @@ function serverTime() { $('form', hash.w).ajaxForm({ target: target, - success: function() { - setTimeout(function() { + success: function() { + setTimeout(function() { $('#set-window').jqmHide(); }, 1000)} }); } }); - + $('a.remove-from-shelf').click(function(event) { event.preventDefault(); link = $(this); @@ -395,18 +395,18 @@ function serverTime() { link.parent().remove(); }); }); - + $('#share-shelf').hide().addClass('hidden'); $('#share-shelf input').focus(function(){this.select();}); - + $('#user-info').show(); changeBannerText(); $('#onepercent-banner').show(); - + var formatsDownloaded = false; $('#download-shelf').click(function() { $('#download-shelf-menu').slideDown('fast'); - + if (!formatsDownloaded) { // Get info about the formats formatsDownloaded = true; @@ -435,7 +435,7 @@ function serverTime() { } return false; }); - + $('#download-formats-form-cancel').click(function() { $('#download-shelf-menu').slideUp('fast'); return false; diff --git a/wolnelektury/static/js/jquery.autocomplete.js b/wolnelektury/static/js/jquery.autocomplete.js index 4e425e8bd..6f46e1b9c 100644 --- a/wolnelektury/static/js/jquery.autocomplete.js +++ b/wolnelektury/static/js/jquery.autocomplete.js @@ -16,7 +16,7 @@ */ ;(function($) { - + $.fn.extend({ autocomplete: function(urlOrData, options) { var isUrl = typeof urlOrData == "string"; @@ -26,13 +26,13 @@ $.fn.extend({ delay: isUrl ? $.Autocompleter.defaults.delay : 10, max: options && !options.scroll ? 10 : 150 }, options); - + // if highlight is set to false, replace it with a do-nothing function options.highlight = options.highlight || function(value) { return value; }; - + // if the formatMatch option is not specified, then use formatItem for backwards compatibility options.formatMatch = options.formatMatch || options.formatItem; - + return this.each(function() { new $.Autocompleter(this, options); }); @@ -81,9 +81,9 @@ $.Autocompleter = function(input, options) { mouseDownOnSelect: false }; var select = $.Autocompleter.Select(options, input, selectCurrent, config); - + var blockSubmit; - + // prevent form submit in opera when selecting with return key $.browser.opera && $(input.form).bind("submit.autocomplete", function() { if (blockSubmit) { @@ -91,7 +91,7 @@ $.Autocompleter = function(input, options) { return false; } }); - + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { // a keypress means the input has focus @@ -100,7 +100,7 @@ $.Autocompleter = function(input, options) { // track last key pressed lastKeyPressCode = event.keyCode; switch(event.keyCode) { - + case KEY.UP: event.preventDefault(); if ( select.visible() ) { @@ -109,7 +109,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.DOWN: event.preventDefault(); if ( select.visible() ) { @@ -118,7 +118,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.PAGEUP: event.preventDefault(); if ( select.visible() ) { @@ -127,7 +127,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + case KEY.PAGEDOWN: event.preventDefault(); if ( select.visible() ) { @@ -136,7 +136,7 @@ $.Autocompleter = function(input, options) { onChange(0, true); } break; - + // matches also semicolon case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: case KEY.TAB: @@ -148,11 +148,11 @@ $.Autocompleter = function(input, options) { return false; } break; - + case KEY.ESC: select.hide(); break; - + default: clearTimeout(timeout); timeout = setTimeout(onChange, options.delay); @@ -203,16 +203,16 @@ $.Autocompleter = function(input, options) { $input.unbind(); $(input.form).unbind(".autocomplete"); }); - - + + function selectCurrent() { var selected = select.selected(); if( !selected ) return false; - + var v = selected.result; previousValue = v; - + if ( options.multiple ) { var words = trimWords($input.val()); if ( words.length > 1 ) { @@ -234,26 +234,26 @@ $.Autocompleter = function(input, options) { } v += options.multipleSeparator; } - + $input.val(v); hideResultsNow(); $input.trigger("result", [selected.data, selected.value]); return true; } - + function onChange(crap, skipPrevCheck) { if( lastKeyPressCode == KEY.DEL ) { select.hide(); return; } - + var currentValue = $input.val(); - + if ( !skipPrevCheck && currentValue == previousValue ) return; - + previousValue = currentValue; - + currentValue = lastWord(currentValue); if ( currentValue.length >= options.minChars) { $input.addClass(options.loadingClass); @@ -265,7 +265,7 @@ $.Autocompleter = function(input, options) { select.hide(); } }; - + function trimWords(value) { if (!value) return [""]; @@ -275,12 +275,12 @@ $.Autocompleter = function(input, options) { return $.trim(value).length ? $.trim(word) : null; }); } - + function lastWord(value) { if ( !options.multiple ) return value; var words = trimWords(value); - if (words.length == 1) + if (words.length == 1) return words[0]; var cursorAt = $(input).selection().start; if (cursorAt == value.length) { @@ -290,7 +290,7 @@ $.Autocompleter = function(input, options) { } return words[words.length - 1]; } - + // fills in the input box w/the first match (assumed to be the best match) // q: the term entered // sValue: the first matching result @@ -355,14 +355,14 @@ $.Autocompleter = function(input, options) { success(term, data); // if an AJAX url has been supplied, try loading the data now } else if( (typeof options.url == "string") && (options.url.length > 0) ){ - + var extraParams = { timestamp: +new Date() }; $.each(options.extraParams, function(key, param) { extraParams[key] = typeof param == "function" ? param() : param; }); - + $.ajax({ // try to leverage ajaxQueue plugin to abort previous requests mode: "abort", @@ -386,7 +386,7 @@ $.Autocompleter = function(input, options) { failure(term); } }; - + function parse(data) { var parsed = []; var rows = data.split("\n"); @@ -455,9 +455,9 @@ $.Autocompleter.Cache = function(options) { var data = {}; var length = 0; - + function matchSubset(s, sub) { - if (!options.matchCase) + if (!options.matchCase) s = s.toLowerCase(); var i = s.indexOf(sub); if (options.matchContains == "word"){ @@ -467,17 +467,17 @@ $.Autocompleter.Cache = function(options) { if (i == -1) return false; return i == 0 || options.matchContains; }; - + function add(q, value) { if (length > options.cacheLength){ flush(); } - if (!data[q]){ + if (!data[q]){ length++; } data[q] = value; } - + function populate(){ if( !options.data ) return false; // track the matches @@ -486,23 +486,23 @@ $.Autocompleter.Cache = function(options) { // no url was specified, we need to adjust the cache length to make sure it fits the local data store if( !options.url ) options.cacheLength = 1; - + // track all options for minChars = 0 stMatchSets[""] = []; - + // loop through the array and create a lookup structure for ( var i = 0, ol = options.data.length; i < ol; i++ ) { var rawValue = options.data[i]; // if rawValue is a string, make an array otherwise just reference the array rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; - + var value = options.formatMatch(rawValue, i+1, options.data.length); if ( value === false ) continue; - + var firstChar = value.charAt(0).toLowerCase(); // if no lookup array for this character exists, look it up now - if( !stMatchSets[firstChar] ) + if( !stMatchSets[firstChar] ) stMatchSets[firstChar] = []; // if the match is a string @@ -511,7 +511,7 @@ $.Autocompleter.Cache = function(options) { data: rawValue, result: options.formatResult && options.formatResult(rawValue) || value }; - + // push the current match into the set list stMatchSets[firstChar].push(row); @@ -529,15 +529,15 @@ $.Autocompleter.Cache = function(options) { add(i, value); }); } - + // populate any existing data setTimeout(populate, 25); - + function flush(){ data = {}; length = 0; } - + return { flush: flush, add: add, @@ -545,7 +545,7 @@ $.Autocompleter.Cache = function(options) { load: function(q) { if (!options.cacheLength || !length) return null; - /* + /* * if dealing w/local data and matchContains than we must make sure * to loop through all the data collections looking for matches */ @@ -565,9 +565,9 @@ $.Autocompleter.Cache = function(options) { } }); } - } + } return csub; - } else + } else // if the exact item exists, use it if (data[q]){ return data[q]; @@ -595,7 +595,7 @@ $.Autocompleter.Select = function (options, input, select, config) { var CLASSES = { ACTIVE: "ac_over" }; - + var listItems, active = -1, data, @@ -603,7 +603,7 @@ $.Autocompleter.Select = function (options, input, select, config) { needsInit = true, element, list; - + // Create results function init() { if (!needsInit) @@ -613,11 +613,11 @@ $.Autocompleter.Select = function (options, input, select, config) { .addClass(options.resultsClass) .css("position", "absolute") .appendTo(document.body); - + list = $("
      ").appendTo(element).mouseover( function(event) { if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); - $(target(event)).addClass(CLASSES.ACTIVE); + $(target(event)).addClass(CLASSES.ACTIVE); } }).click(function(event) { $(target(event)).addClass(CLASSES.ACTIVE); @@ -630,13 +630,13 @@ $.Autocompleter.Select = function (options, input, select, config) { }).mouseup(function() { config.mouseDownOnSelect = false; }); - + if( options.width > 0 ) element.css("width", options.width); - + needsInit = false; - } - + } + function target(event) { var element = event.target; while(element && element.tagName != "LI") @@ -663,7 +663,7 @@ $.Autocompleter.Select = function (options, input, select, config) { } } }; - + function movePosition(step) { active += step; if (active < 0) { @@ -672,13 +672,13 @@ $.Autocompleter.Select = function (options, input, select, config) { active = 0; } } - + function limitNumberOfItems(available) { return options.max && options.max < available ? options.max : available; } - + function fillList() { list.empty(); var max = limitNumberOfItems(data.length); @@ -700,7 +700,7 @@ $.Autocompleter.Select = function (options, input, select, config) { if ( $.fn.bgiframe ) list.bgiframe(); } - + return { display: function(d, q) { init(); @@ -752,7 +752,7 @@ $.Autocompleter.Select = function (options, input, select, config) { maxHeight: options.scrollHeight, overflow: 'auto' }); - + if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { var listHeight = 0; listItems.each(function() { @@ -765,7 +765,7 @@ $.Autocompleter.Select = function (options, input, select, config) { listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); } } - + } }, selected: function() { diff --git a/wolnelektury/static/js/jquery.countdown-de.js b/wolnelektury/static/js/jquery.countdown-de.js index 0e02ce571..5a3f43d78 100644 --- a/wolnelektury/static/js/jquery.countdown-de.js +++ b/wolnelektury/static/js/jquery.countdown-de.js @@ -1,12 +1,12 @@ -/* http://keith-wood.name/countdown.html - German initialisation for the jQuery countdown extension - Written by Keith Wood (kbwood@virginbroadband.com.au) Jan 2008. */ -(function($) { - $.countdown.regional['de'] = { - labels: ['Jahren', 'Monate', 'Wochen', 'Tage', 'Stunden', 'Minuten', 'Sekunden'], - labels1: ['Jahre', 'Monat', 'Woche', 'Tag', 'Stunde', 'Minute', 'Sekunde'], - compactLabels: ['J', 'M', 'W', 'T'], - whichLabels: null, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['de']); -})(jQuery); +/* http://keith-wood.name/countdown.html + German initialisation for the jQuery countdown extension + Written by Keith Wood (kbwood@virginbroadband.com.au) Jan 2008. */ +(function($) { + $.countdown.regional['de'] = { + labels: ['Jahren', 'Monate', 'Wochen', 'Tage', 'Stunden', 'Minuten', 'Sekunden'], + labels1: ['Jahre', 'Monat', 'Woche', 'Tag', 'Stunde', 'Minute', 'Sekunde'], + compactLabels: ['J', 'M', 'W', 'T'], + whichLabels: null, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['de']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown-es.js b/wolnelektury/static/js/jquery.countdown-es.js index 09657bc45..362f58c7d 100644 --- a/wolnelektury/static/js/jquery.countdown-es.js +++ b/wolnelektury/static/js/jquery.countdown-es.js @@ -1,12 +1,12 @@ -/* http://keith-wood.name/countdown.html - * Spanish initialisation for the jQuery countdown extension - * Written by Sergio Carracedo Martinez webmaster@neodisenoweb.com (2008) */ -(function($) { - $.countdown.regional['es'] = { - labels: ['Años', 'Meses', 'Semanas', 'Dias', 'Horas', 'Minutos', 'Segundos'], - labels1: ['Años', 'Meses', 'Semanas', 'Dias', 'Horas', 'Minutos', 'Segundos'], - compactLabels: ['a', 'm', 's', 'g'], - whichLabels: null, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['es']); -})(jQuery); +/* http://keith-wood.name/countdown.html + * Spanish initialisation for the jQuery countdown extension + * Written by Sergio Carracedo Martinez webmaster@neodisenoweb.com (2008) */ +(function($) { + $.countdown.regional['es'] = { + labels: ['Años', 'Meses', 'Semanas', 'Dias', 'Horas', 'Minutos', 'Segundos'], + labels1: ['Años', 'Meses', 'Semanas', 'Dias', 'Horas', 'Minutos', 'Segundos'], + compactLabels: ['a', 'm', 's', 'g'], + whichLabels: null, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['es']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown-fr.js b/wolnelektury/static/js/jquery.countdown-fr.js index 5f5fac98b..70e17def7 100644 --- a/wolnelektury/static/js/jquery.countdown-fr.js +++ b/wolnelektury/static/js/jquery.countdown-fr.js @@ -1,12 +1,12 @@ -/* http://keith-wood.name/countdown.html - French initialisation for the jQuery countdown extension - Written by Keith Wood (kbwood{at}iinet.com.au) Jan 2008. */ -(function($) { - $.countdown.regional['fr'] = { - labels: ['Années', 'Mois', 'Semaines', 'Jours', 'Heures', 'Minutes', 'Secondes'], - labels1: ['Année', 'Mois', 'Semaine', 'Jour', 'Heure', 'Minute', 'Seconde'], - compactLabels: ['a', 'm', 's', 'j'], - whichLabels: null, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['fr']); -})(jQuery); +/* http://keith-wood.name/countdown.html + French initialisation for the jQuery countdown extension + Written by Keith Wood (kbwood{at}iinet.com.au) Jan 2008. */ +(function($) { + $.countdown.regional['fr'] = { + labels: ['Années', 'Mois', 'Semaines', 'Jours', 'Heures', 'Minutes', 'Secondes'], + labels1: ['Année', 'Mois', 'Semaine', 'Jour', 'Heure', 'Minute', 'Seconde'], + compactLabels: ['a', 'm', 's', 'j'], + whichLabels: null, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['fr']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown-pl.js b/wolnelektury/static/js/jquery.countdown-pl.js index 77a8a172b..6860a4bab 100644 --- a/wolnelektury/static/js/jquery.countdown-pl.js +++ b/wolnelektury/static/js/jquery.countdown-pl.js @@ -1,17 +1,17 @@ -/* http://keith-wood.name/countdown.html - * Polish initialisation for the jQuery countdown extension - * Written by Pawel Lewtak lewtak@gmail.com (2008) */ -(function($) { - $.countdown.regional['pl'] = { - labels: ['lat', 'miesięcy', 'tygodni', 'dni', 'godzin', 'minut', 'sekund'], - labels1: ['rok', 'miesiąc', 'tydzień', 'dzień', 'godzina', 'minuta', 'sekunda'], - labels2: ['lata', 'miesiące', 'tygodnie', 'dni', 'godziny', 'minuty', 'sekundy'], - compactLabels: ['l', 'm', 't', 'd'], compactLabels1: ['r', 'm', 't', 'd'], - whichLabels: function(amount) { - var units = amount % 10; - var tens = Math.floor((amount % 100) / 10); - return (amount == 1 ? 1 : (units >= 2 && units <= 4 && tens != 1 ? 2 : 0)); - }, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['pl']); -})(jQuery); +/* http://keith-wood.name/countdown.html + * Polish initialisation for the jQuery countdown extension + * Written by Pawel Lewtak lewtak@gmail.com (2008) */ +(function($) { + $.countdown.regional['pl'] = { + labels: ['lat', 'miesięcy', 'tygodni', 'dni', 'godzin', 'minut', 'sekund'], + labels1: ['rok', 'miesiąc', 'tydzień', 'dzień', 'godzina', 'minuta', 'sekunda'], + labels2: ['lata', 'miesiące', 'tygodnie', 'dni', 'godziny', 'minuty', 'sekundy'], + compactLabels: ['l', 'm', 't', 'd'], compactLabels1: ['r', 'm', 't', 'd'], + whichLabels: function(amount) { + var units = amount % 10; + var tens = Math.floor((amount % 100) / 10); + return (amount == 1 ? 1 : (units >= 2 && units <= 4 && tens != 1 ? 2 : 0)); + }, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['pl']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown-ru.js b/wolnelektury/static/js/jquery.countdown-ru.js index 1d1ca96e1..badd3eb01 100644 --- a/wolnelektury/static/js/jquery.countdown-ru.js +++ b/wolnelektury/static/js/jquery.countdown-ru.js @@ -1,12 +1,12 @@ -/* http://keith-wood.name/countdown.html - * Russian initialisation for the jQuery countdown extension - * Written by Dominus i3rixon@gmail.com (2008) */ -(function($) { - $.countdown.regional['ru'] = { - labels: ['Лет', 'Месяцев', 'Недель', 'Дней', 'Часов', 'Минут', 'Секунд'], - labels1: ['Год', 'Месяц', 'Неделя', 'День', 'Час', 'Минута', 'Секунда'], - compactLabels: ['l', 'm', 'n', 'd'], compactLabels1: ['g', 'm', 'n', 'd'], - whichLabels: null, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['ru']); -})(jQuery); +/* http://keith-wood.name/countdown.html + * Russian initialisation for the jQuery countdown extension + * Written by Dominus i3rixon@gmail.com (2008) */ +(function($) { + $.countdown.regional['ru'] = { + labels: ['Лет', 'Месяцев', 'Недель', 'Дней', 'Часов', 'Минут', 'Секунд'], + labels1: ['Год', 'Месяц', 'Неделя', 'День', 'Час', 'Минута', 'Секунда'], + compactLabels: ['l', 'm', 'n', 'd'], compactLabels1: ['g', 'm', 'n', 'd'], + whichLabels: null, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['ru']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown-uk.js b/wolnelektury/static/js/jquery.countdown-uk.js index e38ab0479..c98791e53 100644 --- a/wolnelektury/static/js/jquery.countdown-uk.js +++ b/wolnelektury/static/js/jquery.countdown-uk.js @@ -1,12 +1,12 @@ -/* http://keith-wood.name/countdown.html - * Ukrainian initialisation for the jQuery countdown extension - * Written by Goloborodko M misha.gm@gmail.com (2009) */ -(function($) { - $.countdown.regional['uk'] = { - labels: ['Років', 'Місяців', 'Тижднів', 'Днів', 'Годин', 'Хвилин', 'Секунд'], - labels1: ['Рік', 'Місяць', 'Тиждень', 'День', 'Година', 'Хвилина', 'Секунда'], - compactLabels: ['r', 'm', 't', 'd'], - whichLabels: null, - timeSeparator: ':', isRTL: false}; - $.countdown.setDefaults($.countdown.regional['uk']); -})(jQuery); +/* http://keith-wood.name/countdown.html + * Ukrainian initialisation for the jQuery countdown extension + * Written by Goloborodko M misha.gm@gmail.com (2009) */ +(function($) { + $.countdown.regional['uk'] = { + labels: ['Років', 'Місяців', 'Тижднів', 'Днів', 'Годин', 'Хвилин', 'Секунд'], + labels1: ['Рік', 'Місяць', 'Тиждень', 'День', 'Година', 'Хвилина', 'Секунда'], + compactLabels: ['r', 'm', 't', 'd'], + whichLabels: null, + timeSeparator: ':', isRTL: false}; + $.countdown.setDefaults($.countdown.regional['uk']); +})(jQuery); diff --git a/wolnelektury/static/js/jquery.countdown.js b/wolnelektury/static/js/jquery.countdown.js index 27e2f4a10..f9c58d370 100644 --- a/wolnelektury/static/js/jquery.countdown.js +++ b/wolnelektury/static/js/jquery.countdown.js @@ -1,8 +1,8 @@ /* http://keith-wood.name/countdown.html Countdown for jQuery v1.5.8. Written by Keith Wood (kbwood{at}iinet.com.au) January 2008. - Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and - MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and + MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. Please attribute the author if you use it. */ /* Display a countdown timer. @@ -68,12 +68,12 @@ var S = 6; // Seconds $.extend(Countdown.prototype, { /* Class name added to elements to indicate already configured with countdown. */ markerClassName: 'hasCountdown', - + /* Shared timer for all countdowns. */ _timer: setInterval(function() { $.countdown._updateTargets(); }, 980), /* List of currently active countdown targets. */ _timerTargets: [], - + /* Override the default settings for all instances of the countdown widget. @param options (object) the new settings to use as defaults */ setDefaults: function(options) { @@ -277,7 +277,7 @@ $.extend(Countdown.prototype, { } } }, - + /* Calculate interal settings for an instance. @param target (element) the containing division @param inst (object) the current settings for this instance */ @@ -362,7 +362,7 @@ $.extend(Countdown.prototype, { inst[inst._since ? '_since' : '_until'] = this._determineTime(sign + inst._periods[0] + 'y' + sign + inst._periods[1] + 'o' + sign + inst._periods[2] + 'w' + - sign + inst._periods[3] + 'd' + sign + inst._periods[4] + 'h' + + sign + inst._periods[3] + 'd' + sign + inst._periods[4] + 'h' + sign + inst._periods[5] + 'm' + sign + inst._periods[6] + 's'); this._addTarget(target); } @@ -421,7 +421,7 @@ $.extend(Countdown.prototype, { case 'd': day += parseInt(matches[1], 10); break; case 'w': day += parseInt(matches[1], 10) * 7; break; case 'o': - month += parseInt(matches[1], 10); + month += parseInt(matches[1], 10); day = Math.min(day, $.countdown._getDaysInMonth(year, month)); break; case 'y': @@ -508,8 +508,8 @@ $.extend(Countdown.prototype, { return (layout ? this._buildLayout(inst, show, layout, compact, significant, showSignificant) : ((compact ? // Compact version '' + - showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) + + (inst._hold ? ' countdown_holding' : '') + '">' + + showCompact(Y) + showCompact(O) + showCompact(W) + showCompact(D) + (show[H] ? this._minDigits(inst._periods[H], 2) : '') + (show[M] ? (show[H] ? timeSeparator : '') + this._minDigits(inst._periods[M], 2) : '') + @@ -616,7 +616,7 @@ $.extend(Countdown.prototype, { show[S] = (format.match('s') ? '?' : (format.match('S') ? '!' : null)); return show; }, - + /* Calculate the requested periods between now and the target time. @param inst (object) the current settings for this instance @param show (string[7]) flags indicating which periods are requested/required diff --git a/wolnelektury/static/js/jquery.eventdelegation.js b/wolnelektury/static/js/jquery.eventdelegation.js index 5ecba6e3b..52fce074a 100644 --- a/wolnelektury/static/js/jquery.eventdelegation.js +++ b/wolnelektury/static/js/jquery.eventdelegation.js @@ -1,4 +1,4 @@ -/* +/* * jQuery Event Delegation Plugin - jquery.eventdelegation.js * Fast flexible event handling * @@ -21,10 +21,10 @@ 'keydown', 'keypress', 'keyup' - ], function(i, eventName) { + ], function(i, eventName) { allowed[eventName] = true; }); - + $.fn.extend({ delegate: function (event, selector, f) { return $(this).each(function () { @@ -32,7 +32,7 @@ $(this).bind(event, function (e) { var el = $(e.target), result = false; - + while (!$(el).is('body')) { if ($(el).is(selector)) { result = f.apply($(el)[0], [e]); @@ -40,7 +40,7 @@ e.preventDefault(); return; } - + el = $(el).parent(); } }); diff --git a/wolnelektury/static/js/jquery.form.js b/wolnelektury/static/js/jquery.form.js index 659baa989..36af6b199 100644 --- a/wolnelektury/static/js/jquery.form.js +++ b/wolnelektury/static/js/jquery.form.js @@ -13,7 +13,7 @@ (function($) { /* - Usage Note: + Usage Note: ----------- Do not use both ajaxSubmit and ajaxForm on the same form. These functions are intended to be exclusive. Use ajaxSubmit if you want @@ -36,13 +36,13 @@ target: '#output' }); }); - + When using ajaxForm, the ajaxSubmit function will be invoked for you - at the appropriate time. + at the appropriate time. */ /** - * ajaxSubmit() provides a mechanism for immediately submitting + * ajaxSubmit() provides a mechanism for immediately submitting * an HTML form using AJAX. */ $.fn.ajaxSubmit = function(options) { @@ -80,14 +80,14 @@ $.fn.ajaxSubmit = function(options) { if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { log('ajaxSubmit: submit aborted via beforeSubmit callback'); return this; - } + } // fire vetoable 'validate' event this.trigger('form-submit-validate', [a, this, options, veto]); if (veto.veto) { log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); return this; - } + } var q = $.param(a); @@ -125,7 +125,7 @@ $.fn.ajaxSubmit = function(options) { found = true; // options.iframe allows user to force iframe mode - if (options.iframe || found) { + if (options.iframe || found) { // hack to fix Safari hang (thanks to Tim Molendijk for this) // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d if ($.browser.safari && options.closeKeepAlive) @@ -144,19 +144,19 @@ $.fn.ajaxSubmit = function(options) { // private function for handling file uploads (hat tip to YAHOO!) function fileUpload() { var form = $form[0]; - + if ($(':input[@name=submit]', form).length) { alert('Error: Form elements must not be named "submit".'); return; } - + var opts = $.extend({}, $.ajaxSettings, options); var id = 'jqFormIO' + (new Date().getTime()); var $io = $('