X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/e2e94b4feb7f08974999a16c720911d0094c3797..6e102eb7360b2c7df5a8565486e3428e6ccf2ab8:/apps/catalogue/models.py diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 1e6bb95e2..2b6a2cc2f 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -8,10 +8,10 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.core.files import File from django.template.loader import render_to_string -from django.template.defaultfilters import slugify from django.utils.safestring import mark_safe from django.utils.translation import get_language from django.core.urlresolvers import reverse +from django.db.models.signals import post_save, m2m_changed, pre_delete from django.conf import settings @@ -21,6 +21,7 @@ from catalogue.fields import JSONField from librarian import dcparser, html, epub, NoDublinCore from mutagen import id3 +from slughifi import slughifi TAG_CATEGORIES = ( @@ -63,6 +64,9 @@ class Tag(TagBase): gazeta_link = models.CharField(blank=True, max_length=240) wiki_link = models.CharField(blank=True, max_length=240) + class UrlDeprecationWarning(DeprecationWarning): + pass + categories_rev = { 'autor': 'author', 'epoka': 'epoch', @@ -121,19 +125,20 @@ class Tag(TagBase): real_tags = [] ambiguous_slugs = [] category = None + deprecated = False tags_splitted = tags.split('/') - for index, name in enumerate(tags_splitted): - if name in Tag.categories_rev: + for name in tags_splitted: + if category: + real_tags.append(Tag.objects.get(slug=name, category=category)) + category = None + elif name in Tag.categories_rev: category = Tag.categories_rev[name] else: - if category: - real_tags.append(Tag.objects.get(slug=name, category=category)) - category = None - else: - try: - real_tags.append(Tag.objects.exclude(category='book').get(slug=name)) - except Tag.MultipleObjectsReturned, e: - ambiguous_slugs.append(name) + try: + real_tags.append(Tag.objects.exclude(category='book').get(slug=name)) + deprecated = True + except Tag.MultipleObjectsReturned, e: + ambiguous_slugs.append(name) if category: # something strange left off @@ -144,8 +149,11 @@ class Tag(TagBase): e.tags = real_tags e.ambiguous_slugs = ambiguous_slugs raise e - else: - return real_tags + if deprecated: + e = Tag.UrlDeprecationWarning() + e.tags = real_tags + raise e + return real_tags else: return TagBase.get_tag_list(tags) @@ -155,26 +163,37 @@ class Tag(TagBase): # TODO: why is this hard-coded ? -def book_upload_path(ext=None): +def book_upload_path(ext=None, maxlen=100): def get_dynamic_path(media, filename, ext=ext): # how to put related book's slug here? if not ext: - ext = media.type + if media.type == 'daisy': + ext = 'daisy.zip' + else: + ext = media.type if not media.name: - name = slugify(filename.split(".")[0]) + name = slughifi(filename.split(".")[0]) else: - name = slugify(media.name) - return 'lektura/%s.%s' % (name, ext) + name = slughifi(media.name) + return 'lektura/%s.%s' % (name[:maxlen-len('lektura/.%s' % ext)-4], ext) return get_dynamic_path class BookMedia(models.Model): type = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100") - name = models.CharField(_('name'), max_length="100", blank=True) - file = models.FileField(_('file'), upload_to=book_upload_path(), blank=True) + name = models.CharField(_('name'), max_length="100") + file = models.FileField(_('file'), upload_to=book_upload_path()) uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False) extra_info = JSONField(_('extra information'), default='{}') + def book_count(self): + return self.book_set.count() + book_count.short_description = _('book count') + + def books(self): + return mark_safe('
'.join("%s" % (reverse('admin:catalogue_book_change', args=[b.id]), b.title) for b in self.book_set.all())) + books.short_description = _('books') + def __unicode__(self): return "%s (%s)" % (self.name, self.file.name.split("/")[-1]) @@ -183,14 +202,14 @@ class BookMedia(models.Model): verbose_name = _('book media') verbose_name_plural = _('book media') - def save(self, force_insert=False, force_update=False): - media = super(BookMedia, self).save(force_insert, force_update) + def save(self, force_insert=False, force_update=False, **kwargs): + media = super(BookMedia, self).save(force_insert, force_update, **kwargs) if self.type == 'mp3': file = self.file extra_info = self.get_extra_info_value() extra_info.update(self.get_mp3_info()) self.set_extra_info_value(extra_info) - media = super(BookMedia, self).save(force_insert, force_update) + media = super(BookMedia, self).save(force_insert, force_update, **kwargs) return media def get_mp3_info(self): @@ -313,7 +332,7 @@ class Book(models.Model): elif type == "html": return self.html_file elif type == "epub": - return self.html_file + return self.epub_file elif type == "txt": return self.txt_file elif type == "pdf": @@ -452,8 +471,7 @@ class Book(models.Model): epub_file = StringIO() try: epub.transform(BookImportDocProvider(self), self.slug, output_file=epub_file) - self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue()), save=False) - self.save() + self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue())) FileRecord(slug=self.slug, type='epub', sha1=sha1(epub_file.getvalue()).hexdigest()).save() except NoDublinCore: pass @@ -467,9 +485,19 @@ class Book(models.Model): child_book.save() book_descendants += list(child_book.children.all()) + def build_txt(self): + from StringIO import StringIO + from django.core.files.base import ContentFile + from librarian import text + + out = StringIO() + text.transform(open(self.xml_file.path), out) + self.txt_file.save('%s.txt' % self.slug, ContentFile(out.getvalue())) + self.save() + @classmethod - def from_xml_file(cls, xml_file, overwrite=False, build_epub=True): + def from_xml_file(cls, xml_file, **kwargs): # use librarian to parse meta-data book_info = dcparser.parse(xml_file) @@ -477,14 +505,14 @@ class Book(models.Model): xml_file = File(open(xml_file)) try: - return cls.from_text_and_meta(xml_file, book_info, overwrite, build_epub=build_epub) + return cls.from_text_and_meta(xml_file, book_info, **kwargs) finally: xml_file.close() @classmethod - def from_text_and_meta(cls, raw_file, book_info, overwrite=False, build_epub=True): + def from_text_and_meta(cls, raw_file, book_info, overwrite=False, build_epub=True, build_txt=True): + import re from tempfile import NamedTemporaryFile - from slughifi import slughifi from markupstring import MarkupString from django.core.files.storage import default_storage @@ -501,6 +529,8 @@ class Book(models.Model): # Read book metadata book_base, book_slug = book_info.url.rsplit('/', 1) + if re.search(r'[^a-zA-Z0-9-]', book_slug): + raise ValueError('Invalid characters in slug') book, created = Book.objects.get_or_create(slug=book_slug) if created: @@ -535,7 +565,7 @@ class Book(models.Model): tag.save() book_tags.append(tag) - book.tags = book_tags + book_shelves + book.tags = set(book_tags + book_shelves) book_tag = book.book_tag() @@ -591,6 +621,9 @@ class Book(models.Model): new_fragment.save() new_fragment.tags = set(book_tags + themes + [book_tag] + ancestor_tags) + if not settings.NO_BUILD_TXT and build_txt: + book.build_txt() + if not settings.NO_BUILD_EPUB and build_epub: book.root_ancestor.build_epub() @@ -720,6 +753,12 @@ class FileRecord(models.Model): def __unicode__(self): return "%s %s.%s" % (self.sha1, self.slug, self.type) +########### +# +# SIGNALS +# +########### + def _tags_updated_handler(sender, affected_tags, **kwargs): # reset tag global counter @@ -737,3 +776,24 @@ def _tags_updated_handler(sender, affected_tags, **kwargs): sender.book.reset_theme_counter() tags_updated.connect(_tags_updated_handler) + +def _m2m_changed_handler(sender, instance, action, reverse, pk_set, **kwargs): + """ refresh all the short_html stuff on BookMedia delete """ + if sender == Book.medias.through and reverse and action == 'pre_clear': + for book in instance.book_set.all(): + book.save() +m2m_changed.connect(_m2m_changed_handler) + +def _pre_delete_handler(sender, instance, **kwargs): + """ explicitly clear m2m, so that Books can be refreshed """ + if sender == BookMedia: + instance.book_set.clear() +pre_delete.connect(_pre_delete_handler) + +def _post_save_handler(sender, instance, **kwargs): + """ refresh all the short_html stuff on BookMedia update """ + if sender == BookMedia: + for book in instance.book_set.all(): + book.save() +post_save.connect(_post_save_handler) +