Add cover_thumb to tests.
[wolnelektury.git] / src / picture / models.py
index 5aabe3c..643149a 100644 (file)
@@ -10,13 +10,17 @@ from django.conf import settings
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
-from fnpdjango.utils.text.slughifi import slughifi
+from slugify import slugify
 from ssify import flush_ssi_includes
 from ssify import flush_ssi_includes
+
+from catalogue.models.tag import prefetched_relations
+from catalogue.utils import split_tags
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 import itertools
 import logging
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 import itertools
 import logging
+import re
 
 from PIL import Image
 
 
 from PIL import Image
 
@@ -33,14 +37,13 @@ picture_storage = FileSystemStorage(location=path.join(
 class PictureArea(models.Model):
     picture = models.ForeignKey('picture.Picture', related_name='areas')
     area = jsonfield.JSONField(_('area'), default={}, editable=False)
 class PictureArea(models.Model):
     picture = models.ForeignKey('picture.Picture', related_name='areas')
     area = jsonfield.JSONField(_('area'), default={}, editable=False)
-    kind = models.CharField(_('kind'), max_length=10, blank=False,
-                           null=False, db_index=True,
-                           choices=(('thing', _('thing')),
-                                    ('theme', _('theme'))))
-
-    objects     = models.Manager()
-    tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
-    tags        = managers.TagDescriptor(catalogue.models.Tag)
+    kind = models.CharField(
+        _('kind'), max_length=10, blank=False, null=False, db_index=True,
+        choices=(('thing', _('thing')), ('theme', _('theme'))))
+
+    objects = models.Manager()
+    tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
+    tags = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     short_html_url_name = 'picture_area_short'
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     short_html_url_name = 'picture_area_short'
@@ -72,26 +75,27 @@ class Picture(models.Model):
     Picture resource.
 
     """
     Picture resource.
 
     """
-    title       = models.CharField(_('title'), max_length=32767)
-    slug        = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
-    sort_key    = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
-    sort_key_author = models.CharField(_('sort key by author'), max_length=120, db_index=True, editable=False, default=u'')
-    created_at  = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
-    changed_at  = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
-    xml_file    = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
-    image_file  = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
-    html_file   = models.FileField('html_file', upload_to="html", storage=picture_storage)
-    areas_json       = jsonfield.JSONField(_('picture areas JSON'), default={}, editable=False)
-    extra_info    = jsonfield.JSONField(_('extra information'), default={})
-    culturepl_link   = models.CharField(blank=True, max_length=240)
-    wiki_link     = models.CharField(blank=True, max_length=240)
-
-    width       = models.IntegerField(null=True)
-    height      = models.IntegerField(null=True)
-
-    objects     = models.Manager()
-    tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
-    tags        = managers.TagDescriptor(catalogue.models.Tag)
+    title = models.CharField(_('title'), max_length=32767)
+    slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
+    sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
+    sort_key_author = models.CharField(
+        _('sort key by author'), max_length=120, db_index=True, editable=False, default=u'')
+    created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
+    changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
+    xml_file = models.FileField(_('xml file'), upload_to="xml", storage=picture_storage)
+    image_file = ImageField(_('image file'), upload_to="images", storage=picture_storage)
+    html_file = models.FileField(_('html file'), upload_to="html", storage=picture_storage)
+    areas_json = jsonfield.JSONField(_('picture areas JSON'), default={}, editable=False)
+    extra_info = jsonfield.JSONField(_('extra information'), default={})
+    culturepl_link = models.CharField(blank=True, max_length=240)
+    wiki_link = models.CharField(blank=True, max_length=240)
+
+    width = models.IntegerField(null=True)
+    height = models.IntegerField(null=True)
+
+    objects = models.Manager()
+    tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
+    tags = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     short_html_url_name = 'picture_short'
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     short_html_url_name = 'picture_short'
@@ -100,7 +104,7 @@ class Picture(models.Model):
         pass
 
     class Meta:
         pass
 
     class Meta:
-        ordering = ('sort_key',)
+        ordering = ('sort_key_author', 'sort_key')
 
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
@@ -111,8 +115,8 @@ class Picture(models.Model):
         self.sort_key = sortify(self.title)[:120]
 
         try:
         self.sort_key = sortify(self.title)[:120]
 
         try:
-            author = self.tags.filter(category='author')[0].sort_key
-        except IndexError:
+            author = self.authors().first().sort_key
+        except AttributeError:
             author = u''
         self.sort_key_author = author
 
             author = u''
         self.sort_key_author = author
 
@@ -123,12 +127,46 @@ class Picture(models.Model):
     def __unicode__(self):
         return self.title
 
     def __unicode__(self):
         return self.title
 
+    def authors(self):
+        return self.tags.filter(category='author')
+
+    def tag_unicode(self, category):
+        relations = prefetched_relations(self, category)
+        if relations:
+            return ', '.join(rel.tag.name for rel in relations)
+        else:
+            return ', '.join(self.tags.filter(category=category).values_list('name', flat=True))
+
+    def author_unicode(self):
+        return self.tag_unicode('author')
+
+    def tags_by_category(self):
+        return split_tags(self.tags)
+
     @permalink
     def get_absolute_url(self):
     @permalink
     def get_absolute_url(self):
-        return ('picture.views.picture_detail', [self.slug])
+        return 'picture.views.picture_detail', [self.slug]
+
+    def get_initial(self):
+        try:
+            return re.search(r'\w', self.title, re.U).group(0)
+        except AttributeError:
+            return ''
+
+    def get_next(self):
+        try:
+            return type(self).objects.filter(sort_key__gt=self.sort_key)[0]
+        except IndexError:
+            return None
+
+    def get_previous(self):
+        try:
+            return type(self).objects.filter(sort_key__lt=self.sort_key).order_by('-sort_key')[0]
+        except IndexError:
+            return None
 
     @classmethod
 
     @classmethod
-    def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
+    def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False, search_index=True):
         """
         Import xml and it's accompanying image file.
         If image file is missing, it will be fetched by librarian.picture.ImageStore
         """
         Import xml and it's accompanying image file.
         If image file is missing, it will be fetched by librarian.picture.ImageStore
@@ -141,7 +179,6 @@ class Picture(models.Model):
         close_xml_file = False
         close_image_file = False
 
         close_xml_file = False
         close_image_file = False
 
-
         if image_file is not None and not isinstance(image_file, File):
             image_file = File(open(image_file))
             close_image_file = True
         if image_file is not None and not isinstance(image_file, File):
             image_file = File(open(image_file))
             close_image_file = True
@@ -165,10 +202,12 @@ class Picture(models.Model):
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             picture_tags = set(catalogue.models.Tag.tags_from_info(picture_xml.picture_info))
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             picture_tags = set(catalogue.models.Tag.tags_from_info(picture_xml.picture_info))
-            motif_tags = set()
-            thing_tags = set()
+            for tag in picture_tags:
+                if not tag.for_pictures:
+                    tag.for_pictures = True
+                    tag.save()
 
 
-            area_data = {'themes':{}, 'things':{}}
+            area_data = {'themes': {}, 'things': {}}
 
             # Treat all names in picture XML as in default language.
             lang = settings.LANGUAGE_CODE
 
             # Treat all names in picture XML as in default language.
             lang = settings.LANGUAGE_CODE
@@ -180,34 +219,47 @@ class Picture(models.Model):
                 if part.get('object', None) is not None:
                     _tags = set()
                     for objname in part['object'].split(','):
                 if part.get('object', None) is not None:
                     _tags = set()
                     for objname in part['object'].split(','):
-                        objname = objname.strip().capitalize()
-                        tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
+                        objname = objname.strip()
+                        assert objname, 'Empty object name'
+                        # str.capitalize() is wrong, because it also lowers letters
+                        objname = objname[0].upper() + objname[1:]
+                        tag, created = catalogue.models.Tag.objects.get_or_create(
+                            slug=slugify(objname), category='thing')
                         if created:
                             tag.name = objname
                             setattr(tag, 'name_%s' % lang, tag.name)
                             tag.sort_key = sortify(tag.name)
                         if created:
                             tag.name = objname
                             setattr(tag, 'name_%s' % lang, tag.name)
                             tag.sort_key = sortify(tag.name)
+                            tag.for_pictures = True
                             tag.save()
                             tag.save()
-                        #thing_tags.add(tag)
                         area_data['things'][tag.slug] = {
                             'object': objname,
                             'coords': part['coords'],
                             }
 
                         _tags.add(tag)
                         area_data['things'][tag.slug] = {
                             'object': objname,
                             'coords': part['coords'],
                             }
 
                         _tags.add(tag)
+                        if not tag.for_pictures:
+                            tag.for_pictures = True
+                            tag.save()
                     area = PictureArea.rectangle(picture, 'thing', part['coords'])
                     area.save()
                     area = PictureArea.rectangle(picture, 'thing', part['coords'])
                     area.save()
+                    # WTF thing area does not inherit tags from picture and theme area does, is it intentional?
                     area.tags = _tags
                 else:
                     _tags = set()
                     for motifs in part['themes']:
                         for motif in motifs.split(','):
                     area.tags = _tags
                 else:
                     _tags = set()
                     for motifs in part['themes']:
                         for motif in motifs.split(','):
-                            tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
+                            tag, created = catalogue.models.Tag.objects.get_or_create(
+                                slug=slugify(motif), category='theme')
                             if created:
                                 tag.name = motif
                                 tag.sort_key = sortify(tag.name)
                             if created:
                                 tag.name = motif
                                 tag.sort_key = sortify(tag.name)
+                                tag.for_pictures = True
                                 tag.save()
                                 tag.save()
-                            #motif_tags.add(tag)
+                            # motif_tags.add(tag)
                             _tags.add(tag)
                             _tags.add(tag)
+                            if not tag.for_pictures:
+                                tag.for_pictures = True
+                                tag.save()
                             area_data['themes'][tag.slug] = {
                                 'theme': motif,
                                 'coords': part['coords']
                             area_data['themes'][tag.slug] = {
                                 'theme': motif,
                                 'coords': part['coords']
@@ -218,7 +270,7 @@ class Picture(models.Model):
                     area.save()
                     area.tags = _tags.union(picture_tags)
 
                     area.save()
                     area.tags = _tags.union(picture_tags)
 
-            picture.tags = picture_tags.union(motif_tags).union(thing_tags)
+            picture.tags = picture_tags
             picture.areas_json = area_data
 
             if image_file is not None:
             picture.areas_json = area_data
 
             if image_file is not None:
@@ -239,6 +291,8 @@ class Picture(models.Model):
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
             tasks.generate_picture_html(picture.id)
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
             tasks.generate_picture_html(picture.id)
+            if not settings.NO_SEARCH_INDEX and search_index:
+                tasks.index_picture.delay(picture.id, picture_info=picture_xml.picture_info)
 
         if close_xml_file:
             xml_file.close()
 
         if close_xml_file:
             xml_file.close()
@@ -260,10 +314,7 @@ class Picture(models.Model):
         from PIL import ImageDraw, ImageFont
         from librarian import get_resource
 
         from PIL import ImageDraw, ImageFont
         from librarian import get_resource
 
-        annotated = Image.new(img.mode,
-                (img.size[0], img.size[1] + 40),
-                (255, 255, 255)
-            )
+        annotated = Image.new(img.mode, (img.size[0], img.size[1] + 40), (255, 255, 255))
         annotated.paste(img, (0, 0))
         annotation = Image.new('RGB', (img.size[0] * 3, 120), (255, 255, 255))
         ImageDraw.Draw(annotation).text(
         annotated.paste(img, (0, 0))
         annotation = Image.new('RGB', (img.size[0] * 3, 120), (255, 255, 255))
         ImageDraw.Draw(annotation).text(
@@ -275,14 +326,14 @@ class Picture(models.Model):
         annotated.paste(annotation.resize((img.size[0], 40), Image.ANTIALIAS), (0, img.size[1]))
         return annotated
 
         annotated.paste(annotation.resize((img.size[0], 40), Image.ANTIALIAS), (0, img.size[1]))
         return annotated
 
+    # WTF/unused
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
         Pictures are optionally filtered with a test function.
         """
 
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
         Pictures are optionally filtered with a test function.
         """
 
-        pics = cls.objects.all().order_by('sort_key')\
-            .only('title', 'slug', 'image_file')
+        pics = cls.objects.all().order_by('sort_key').only('title', 'slug', 'image_file')
 
         if filter:
             pics = pics.filter(filter).distinct()
 
         if filter:
             pics = pics.filter(filter).distinct()
@@ -293,7 +344,7 @@ class Picture(models.Model):
             pics_by_author[tag] = []
 
         for pic in pics.iterator():
             pics_by_author[tag] = []
 
         for pic in pics.iterator():
-            authors = list(pic.tags.filter(category='author'))
+            authors = list(pic.authors().only('pk'))
             if authors:
                 for author in authors:
                     pics_by_author[author].append(pic)
             if authors:
                 for author in authors:
                     pics_by_author[author].append(pic)
@@ -312,9 +363,7 @@ class Picture(models.Model):
         return self._info
 
     def pretty_title(self, html_links=False):
         return self._info
 
     def pretty_title(self, html_links=False):
-        picture = self
-        names = [(tag.name, tag.get_absolute_url())
-                 for tag in self.tags.filter(category='author')]
+        names = [(tag.name, tag.get_absolute_url()) for tag in self.authors().only('name', 'category', 'slug')]
         names.append((self.title, self.get_absolute_url()))
 
         if html_links:
         names.append((self.title, self.get_absolute_url()))
 
         if html_links:
@@ -340,3 +389,17 @@ class Picture(models.Model):
                 ]
             for lang in languages
             ])
                 ]
             for lang in languages
             ])
+
+    def search_index(self, picture_info=None, index=None, index_tags=True, commit=True):
+        if index is None:
+            from search.index import Index
+            index = Index()
+        try:
+            index.index_picture(self, picture_info)
+            if index_tags:
+                index.index_tags()
+            if commit:
+                index.index.commit()
+        except Exception, e:
+            index.index.rollback()
+            raise e