librarian bump
[redakcja.git] / apps / catalogue / models / book.py
index d60a3b6..90327b2 100755 (executable)
@@ -4,18 +4,14 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.contrib.sites.models import Site
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.contrib.sites.models import Site
-from django.db import models
+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 django.template.loader import render_to_string
 from django.utils.translation import ugettext_lazy as _
 from slughifi import slughifi
-from librarian import NoDublinCore, ParseError, ValidationError
-from librarian.dcparser import BookInfo
 
 
-import apiclient
 from catalogue.helpers import cached_in_field
 from catalogue.models import BookPublishRecord, ChunkPublishRecord
 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
 
 
 from catalogue.xml_tools import compile_text, split_xml
 
 
@@ -24,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)
 
     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)
     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)
 
     # 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'
 
     class NoTextError(BaseException):
         pass
 
     class Meta:
         app_label = 'catalogue'
-        ordering = ['parent_number', 'title']
+        ordering = ['title', 'slug']
         verbose_name = _('book')
         verbose_name_plural = _('books')
 
         verbose_name = _('book')
         verbose_name_plural = _('books')
 
@@ -74,11 +70,20 @@ class Book(models.Model):
     def get_absolute_url(self):
         return ("catalogue_book", [self.slug])
 
     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
     # =======================
 
 
     # Creating & manipulating
     # =======================
 
+    def accessible(self, request):
+        return self.public or request.user.is_authenticated()
+
     @classmethod
     @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)
     def create(cls, creator, text, *args, **kwargs):
         b = cls.objects.create(*args, **kwargs)
         b.chunk_set.all().update(creator=creator)
@@ -90,6 +95,7 @@ class Book(models.Model):
         return self.chunk_set.reverse()[0].split(*args, **kwargs)
 
     @classmethod
         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."""
     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."""
@@ -136,6 +142,7 @@ class Book(models.Model):
             i += 1
         return new_slug
 
             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
     def append(self, other, slugs=None, titles=None):
         """Add all chunks of another book to self."""
         assert self != other
@@ -183,6 +190,22 @@ class Book(models.Model):
         assert not other.chunk_set.exists()
         other.delete()
 
         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()
+
 
     # State & cache
     # =============
 
     # State & cache
     # =============
@@ -201,6 +224,9 @@ class Book(models.Model):
             raise AssertionError(_('Not all chunks have publishable revisions.'))
         book_xml = self.materialize(changes=changes)
 
             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:
         try:
             bi = BookInfo.from_string(book_xml.encode('utf-8'))
         except ParseError, e:
@@ -210,7 +236,7 @@ class Book(models.Model):
         except ValidationError, e:
             raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e))
 
         except ValidationError, e:
             raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e))
 
-        valid_about = "http://%s%s" % (Site.objects.get_current().domain, self.get_absolute_url())
+        valid_about = self.correct_about()
         assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about
 
     def hidden(self):
         assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about
 
     def hidden(self):
@@ -247,7 +273,33 @@ class Book(models.Model):
     def short_html(self):
         return render_to_string('catalogue/book_list/book.html', {'book': self})
 
     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):
     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(),
         update = {
             "_new_publishable": self.is_new_publishable(),
             "_published": self.is_published(),
@@ -296,6 +348,9 @@ class Book(models.Model):
         """
             Publishes a book on behalf of a (local) user.
         """
         """
             Publishes a book on behalf of a (local) user.
         """
+        import apiclient
+        from catalogue.signals import post_publish
+
         self.assert_publishable()
         changes = self.get_current_changes(publishable=True)
         book_xml = self.materialize(changes=changes)
         self.assert_publishable()
         changes = self.get_current_changes(publishable=True)
         book_xml = self.materialize(changes=changes)