X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/4ac9918f152cc49badd4a359bfed41e723161cdf..0a2221a9af54f60f93f0d9799a4b87a5774667d8:/apps/catalogue/models/book.py diff --git a/apps/catalogue/models/book.py b/apps/catalogue/models/book.py index fb593889..90327b26 100755 --- a/apps/catalogue/models/book.py +++ b/apps/catalogue/models/book.py @@ -3,14 +3,15 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.db import models +from django.contrib.sites.models import Site +from django.db import models, transaction from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from slughifi import slughifi + from catalogue.helpers import cached_in_field from catalogue.models import BookPublishRecord, ChunkPublishRecord -from catalogue.signals import post_publish -from catalogue.tasks import refresh_instance +from catalogue.tasks import refresh_instance, book_content_updated from catalogue.xml_tools import compile_text, split_xml @@ -19,27 +20,27 @@ class Book(models.Model): title = models.CharField(_('title'), max_length=255, db_index=True) slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True) + public = models.BooleanField(_('public'), default=True, db_index=True) gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True) #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False) - parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children") - parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True) + parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children", editable=False) + parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True, editable=False) # Cache _short_html = models.TextField(null=True, blank=True, editable=False) _single = models.NullBooleanField(editable=False, db_index=True) _new_publishable = models.NullBooleanField(editable=False) _published = models.NullBooleanField(editable=False) - - # Managers - objects = models.Manager() + dc_slug = models.CharField(max_length=128, null=True, blank=True, + editable=False, db_index=True) class NoTextError(BaseException): pass class Meta: app_label = 'catalogue' - ordering = ['parent_number', 'title'] + ordering = ['title', 'slug'] verbose_name = _('book') verbose_name_plural = _('books') @@ -69,11 +70,20 @@ class Book(models.Model): def get_absolute_url(self): return ("catalogue_book", [self.slug]) + def correct_about(self): + return "http://%s%s" % ( + Site.objects.get_current().domain, + self.get_absolute_url() + ) # Creating & manipulating # ======================= + def accessible(self, request): + return self.public or request.user.is_authenticated() + @classmethod + @transaction.commit_on_success def create(cls, creator, text, *args, **kwargs): b = cls.objects.create(*args, **kwargs) b.chunk_set.all().update(creator=creator) @@ -85,6 +95,7 @@ class Book(models.Model): return self.chunk_set.reverse()[0].split(*args, **kwargs) @classmethod + @transaction.commit_on_success def import_xml_text(cls, text=u'', previous_book=None, commit_args=None, **kwargs): """Imports a book from XML, splitting it into chunks as necessary.""" @@ -113,7 +124,7 @@ class Book(models.Model): chunk.title = title[:255] chunk.save() else: - chunk = instance.add(slug, title, adjust_slug=True) + chunk = instance.add(slug, title) chunk.commit(text, **commit_args) @@ -125,14 +136,17 @@ class Book(models.Model): """ slugs = set(c.slug for c in self) i = 1 - new_slug = proposed + new_slug = proposed[:50] while new_slug in slugs: - new_slug = "%s_%d" % (proposed, i) + new_slug = "%s_%d" % (proposed[:45], i) i += 1 return new_slug + @transaction.commit_on_success def append(self, other, slugs=None, titles=None): """Add all chunks of another book to self.""" + assert self != other + number = self[len(self) - 1].number + 1 len_other = len(other) single = len_other == 1 @@ -161,12 +175,11 @@ class Book(models.Model): # just use the guessed title and original book slug chunk.title = other_title_part if other.slug.startswith(self.slug): - chunk_slug = other.slug[len(self.slug):].lstrip('-_') + chunk.slug = other.slug[len(self.slug):].lstrip('-_') else: - chunk_slug = other.slug - chunk.slug = self.make_chunk_slug(chunk_slug) + chunk.slug = other.slug else: - chunk.title = "%s, %s" % (other_title_part, chunk.title) + chunk.title = ("%s, %s" % (other_title_part, chunk.title))[:255] else: chunk.slug = slugs[i] chunk.title = titles[i] @@ -174,6 +187,23 @@ class Book(models.Model): chunk.slug = self.make_chunk_slug(chunk.slug) chunk.save() number += 1 + assert not other.chunk_set.exists() + other.delete() + + @transaction.commit_on_success + def prepend_history(self, other): + """Prepend history from all the other book's chunks to own.""" + assert self != other + + for i in range(len(self), len(other)): + title = u"pusta część %d" % i + chunk = self.add(slughifi(title), title) + chunk.commit('') + + for i in range(len(other)): + self[i].prepend_history(other[0]) + + assert not other.chunk_set.exists() other.delete() @@ -186,13 +216,28 @@ class Book(models.Model): except IndexError: return None - def publishable(self): - if not self.chunk_set.exists(): - return False - for chunk in self: - if not chunk.publishable(): - return False - return True + def assert_publishable(self): + assert self.chunk_set.exists(), _('No chunks in the book.') + try: + changes = self.get_current_changes(publishable=True) + except self.NoTextError: + raise AssertionError(_('Not all chunks have publishable revisions.')) + book_xml = self.materialize(changes=changes) + + from librarian.dcparser import BookInfo + from librarian import NoDublinCore, ParseError, ValidationError + + try: + bi = BookInfo.from_string(book_xml.encode('utf-8')) + except ParseError, e: + raise AssertionError(_('Invalid XML') + ': ' + str(e)) + except NoDublinCore: + raise AssertionError(_('No Dublin Core found.')) + except ValidationError, e: + raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e)) + + valid_about = self.correct_about() + assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about def hidden(self): return self.slug.startswith('.') @@ -228,7 +273,33 @@ class Book(models.Model): def short_html(self): return render_to_string('catalogue/book_list/book.html', {'book': self}) + def book_info(self, publishable=True): + try: + book_xml = self.materialize(publishable=publishable) + except self.NoTextError: + pass + else: + from librarian.dcparser import BookInfo + from librarian import NoDublinCore, ParseError, ValidationError + try: + return BookInfo.from_string(book_xml.encode('utf-8')) + except (self.NoTextError, ParseError, NoDublinCore, ValidationError): + return None + + def refresh_dc_cache(self): + update = { + 'dc_slug': None, + } + + info = self.book_info() + if info is not None: + update['dc_slug'] = info.slug + Book.objects.filter(pk=self.pk).update(**update) + def touch(self): + # this should only really be done when text or publishable status changes + book_content_updated.delay(self) + update = { "_new_publishable": self.is_new_publishable(), "_published": self.is_published(), @@ -277,13 +348,13 @@ class Book(models.Model): """ Publishes a book on behalf of a (local) user. """ - raise NotImplementedError("Publishing not possible yet.") - - from apiclient import api_call + import apiclient + from catalogue.signals import post_publish + self.assert_publishable() changes = self.get_current_changes(publishable=True) book_xml = self.materialize(changes=changes) - #api_call(user, "books", {"book_xml": book_xml}) + apiclient.api_call(user, "books/", {"book_xml": book_xml}) # record the publish br = BookPublishRecord.objects.create(book=self, user=user) for c in changes: