fix in librarian
[wolnelektury.git] / apps / picture / models.py
index 2703c80..5aabe3c 100644 (file)
@@ -1,24 +1,22 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
 from django.db import models, transaction
 import catalogue.models
 from django.db.models import permalink
 from sorl.thumbnail import ImageField
 from django.conf import settings
 from django.db import models, transaction
 import catalogue.models
 from django.db.models import permalink
 from sorl.thumbnail import ImageField
 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.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
-from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
-from django.core.cache import get_cache
-from catalogue.utils import split_tags, related_tag_name
-from django.utils.safestring import mark_safe
 from fnpdjango.utils.text.slughifi import slughifi
 from fnpdjango.utils.text.slughifi import slughifi
+from ssify import flush_ssi_includes
 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
-logging.basicConfig(level=logging.DEBUG)
-from sorl.thumbnail import get_thumbnail, default
-from .engine import CustomCroppingEngine
 
 from PIL import Image
 
 
 from PIL import Image
 
@@ -35,14 +33,17 @@ 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')), 
+    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)
                                     ('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'
 
     @classmethod
     def rectangle(cls, picture, kind, coords):
 
     @classmethod
     def rectangle(cls, picture, kind, coords):
@@ -52,10 +53,18 @@ class PictureArea(models.Model):
         pa.area = coords
         return pa
 
         pa.area = coords
         return pa
 
-    def short_html(self):
-        short_html = unicode(render_to_string(
-                'picture/picturearea_short.html', {'area': self}))
-        return mark_safe(short_html)
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/pa/%d/short.%s.html',
+                ]
+            for lang in languages
+            ])
 
 
 class Picture(models.Model):
 
 
 class Picture(models.Model):
@@ -63,9 +72,10 @@ class Picture(models.Model):
     Picture resource.
 
     """
     Picture resource.
 
     """
-    title       = models.CharField(_('title'), max_length=120)
+    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)
     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)
     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)
@@ -82,6 +92,9 @@ class Picture(models.Model):
     objects     = models.Manager()
     tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     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'
 
     class AlreadyExists(Exception):
         pass
 
     class AlreadyExists(Exception):
         pass
@@ -92,15 +105,18 @@ class Picture(models.Model):
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
-    def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
+    def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
 
         from sortify import sortify
 
-        self.sort_key = sortify(self.title)
+        self.sort_key = sortify(self.title)[:120]
 
 
-        ret = super(Picture, self).save(force_insert, force_update)
+        try:
+            author = self.tags.filter(category='author')[0].sort_key
+        except IndexError:
+            author = u''
+        self.sort_key_author = author
 
 
-        if reset_short_html:
-            self.reset_short_html()
+        ret = super(Picture, self).save(force_insert, force_update)
 
         return ret
 
 
         return ret
 
@@ -133,14 +149,14 @@ class Picture(models.Model):
         if not isinstance(xml_file, File):
             xml_file = File(open(xml_file))
             close_xml_file = True
         if not isinstance(xml_file, File):
             xml_file = File(open(xml_file))
             close_xml_file = True
-        
-        try:
+
+        with transaction.atomic():
             # use librarian to parse meta-data
             if image_store is None:
                 image_store = ImageStore(picture_storage.path('images'))
             picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
 
             # use librarian to parse meta-data
             if image_store is None:
                 image_store = ImageStore(picture_storage.path('images'))
             picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
 
-            picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
+            picture, created = Picture.objects.get_or_create(slug=picture_xml.slug[:120])
             if not created and not overwrite:
                 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
 
             if not created and not overwrite:
                 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
 
@@ -148,53 +164,61 @@ class Picture(models.Model):
             picture.title = unicode(picture_xml.picture_info.title)
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             picture.title = unicode(picture_xml.picture_info.title)
             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()
             motif_tags = set()
             thing_tags = set()
+
             area_data = {'themes':{}, 'things':{}}
 
             area_data = {'themes':{}, 'things':{}}
 
+            # Treat all names in picture XML as in default language.
+            lang = settings.LANGUAGE_CODE
+
             for part in picture_xml.partiter():
                 if picture_xml.frame:
                     c = picture_xml.frame[0]
                     part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
                 if part.get('object', None) is not None:
             for part in picture_xml.partiter():
                 if picture_xml.frame:
                     c = picture_xml.frame[0]
                     part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
                 if part.get('object', None) is not None:
-                    objname = part['object']
-                    tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
-                    if created:
-                        tag.name = objname
-                        tag.sort_key = sortify(tag.name)
-                        tag.save()
-                    #thing_tags.add(tag)
-                    area_data['things'][tag.slug] = {
-                        'object': part['object'],
-                        'coords': part['coords'],
-                        }
-                    area = PictureArea.rectangle(picture, 'thing', part['coords'])
-                    area.save()
-                    _tags = set()
-                    _tags.add(tag)
-                    area.tags = _tags
-                else:
                     _tags = set()
                     _tags = set()
-                    for motif in part['themes']:
-                        tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
+                    for objname in part['object'].split(','):
+                        objname = objname.strip().capitalize()
+                        tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
                         if created:
                         if created:
-                            tag.name = motif
+                            tag.name = objname
+                            setattr(tag, 'name_%s' % lang, tag.name)
                             tag.sort_key = sortify(tag.name)
                             tag.save()
                             tag.sort_key = sortify(tag.name)
                             tag.save()
-                        #motif_tags.add(tag)
-                        _tags.add(tag)
-                        area_data['themes'][tag.slug] = {
-                            'theme': motif,
-                            'coords': part['coords']
+                        #thing_tags.add(tag)
+                        area_data['things'][tag.slug] = {
+                            'object': objname,
+                            'coords': part['coords'],
                             }
 
                             }
 
+                        _tags.add(tag)
+                    area = PictureArea.rectangle(picture, 'thing', part['coords'])
+                    area.save()
+                    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')
+                            if created:
+                                tag.name = motif
+                                tag.sort_key = sortify(tag.name)
+                                tag.save()
+                            #motif_tags.add(tag)
+                            _tags.add(tag)
+                            area_data['themes'][tag.slug] = {
+                                'theme': motif,
+                                'coords': part['coords']
+                                }
+
                     logging.debug("coords for theme: %s" % part['coords'])
                     area = PictureArea.rectangle(picture, 'theme', part['coords'])
                     area.save()
                     logging.debug("coords for theme: %s" % part['coords'])
                     area = PictureArea.rectangle(picture, 'theme', part['coords'])
                     area.save()
-                    area.tags = _tags
+                    area.tags = _tags.union(picture_tags)
 
 
-            picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
-                list(motif_tags) + list(thing_tags)
+            picture.tags = picture_tags.union(motif_tags).union(thing_tags)
             picture.areas_json = area_data
 
             if image_file is not None:
             picture.areas_json = area_data
 
             if image_file is not None:
@@ -203,10 +227,12 @@ class Picture(models.Model):
                 img = picture_xml.image_file()
 
             modified = cls.crop_to_frame(picture_xml, img)
                 img = picture_xml.image_file()
 
             modified = cls.crop_to_frame(picture_xml, img)
+            modified = cls.add_source_note(picture_xml, modified)
+
             picture.width, picture.height = modified.size
 
             modified_file = StringIO()
             picture.width, picture.height = modified.size
 
             modified_file = StringIO()
-            modified.save(modified_file, format='png', quality=95)
+            modified.save(modified_file, format='JPEG', quality=95)
             # FIXME: hardcoded extension - detect from DC format or orginal filename
             picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
 
             # FIXME: hardcoded extension - detect from DC format or orginal filename
             picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
 
@@ -214,29 +240,41 @@ class Picture(models.Model):
             picture.save()
             tasks.generate_picture_html(picture.id)
 
             picture.save()
             tasks.generate_picture_html(picture.id)
 
-        except Exception, ex:
-            logging.exception("Exception during import, rolling back")
-            transaction.rollback()
-            raise ex
-
-        finally:
-            if close_xml_file:
-                xml_file.close()
-            if close_image_file:
-                image_file.close()
-
-        transaction.commit()
+        if close_xml_file:
+            xml_file.close()
+        if close_image_file:
+            image_file.close()
 
         return picture
 
     @classmethod
     def crop_to_frame(cls, wlpic, image_file):
         img = Image.open(image_file)
 
         return picture
 
     @classmethod
     def crop_to_frame(cls, wlpic, image_file):
         img = Image.open(image_file)
-        if wlpic.frame is None:
+        if wlpic.frame is None or wlpic.frame == [[0, 0], [-1, -1]]:
             return img
         img = img.crop(itertools.chain(*wlpic.frame))
         return img
 
             return img
         img = img.crop(itertools.chain(*wlpic.frame))
         return img
 
+    @staticmethod
+    def add_source_note(wlpic, img):
+        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.paste(img, (0, 0))
+        annotation = Image.new('RGB', (img.size[0] * 3, 120), (255, 255, 255))
+        ImageDraw.Draw(annotation).text(
+            (30, 15),
+            wlpic.picture_info.source_name,
+            (0, 0, 0),
+            font=ImageFont.truetype(get_resource("fonts/DejaVuSerif.ttf"), 75)
+        )
+        annotated.paste(annotation.resize((img.size[0], 40), Image.ANTIALIAS), (0, img.size[1]))
+        return annotated
+
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
@@ -273,39 +311,9 @@ class Picture(models.Model):
             self._info = info
         return self._info
 
             self._info = info
         return self._info
 
-    def reset_short_html(self):
-        if self.id is None:
-            return
-
-        cache_key = "Picture.short_html/%d" % (self.id)
-        get_cache('permanent').delete(cache_key)
-
-    def short_html(self):
-        if self.id:
-            cache_key = "Picture.short_html/%d" % (self.id)
-            short_html = get_cache('permanent').get(cache_key)
-        else:
-            short_html = None
-
-        if short_html is not None:
-            return mark_safe(short_html)
-        else:
-            tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
-            tags = split_tags(tags)
-
-            short_html = unicode(render_to_string(
-                    'picture/picture_short.html',
-                    {'picture': self, 'tags': tags}))
-
-            if self.id:
-                get_cache('permanent').set(cache_key, short_html)
-            return mark_safe(short_html)
-
     def pretty_title(self, html_links=False):
         picture = self
     def pretty_title(self, html_links=False):
         picture = self
-        # TODO Add translations (related_tag_info)
-        names = [(tag.name,
-                  catalogue.models.Tag.create_url('author', tag.slug))
+        names = [(tag.name, tag.get_absolute_url())
                  for tag in self.tags.filter(category='author')]
         names.append((self.title, self.get_absolute_url()))
 
                  for tag in self.tags.filter(category='author')]
         names.append((self.title, self.get_absolute_url()))
 
@@ -314,3 +322,21 @@ class Picture(models.Model):
         else:
             names = [tag[0] for tag in names]
         return ', '.join(names)
         else:
             names = [tag[0] for tag in names]
         return ', '.join(names)
+
+    def related_themes(self):
+        return catalogue.models.Tag.objects.usage_for_queryset(
+            self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))
+
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/p/%d/short.%s.html',
+                '/katalog/p/%d/mini.%s.html',
+                ]
+            for lang in languages
+            ])