Add Book.ancestor m2m.
[wolnelektury.git] / apps / picture / models.py
index 65002ae..75ea4c3 100644 (file)
@@ -1,32 +1,92 @@
+# -*- 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.template.loader import render_to_string
 from django.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
 from django.template.loader import render_to_string
-from django.core.cache import get_cache
-from catalogue.utils import split_tags
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.core.cache import caches
+from catalogue.utils import split_tags
 from fnpdjango.utils.text.slughifi import slughifi
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 import itertools
 from fnpdjango.utils.text.slughifi import slughifi
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 import itertools
+import logging
 
 from PIL import Image
 
 
 from PIL import Image
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import get_language, ugettext_lazy as _
 from newtagging import managers
 from os import path
 
 
 from newtagging import managers
 from os import path
 
 
+permanent_cache = caches['permanent']
+
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
         base_url=settings.MEDIA_URL + "pictures/")
 
 
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
         base_url=settings.MEDIA_URL + "pictures/")
 
 
+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)
+    tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
+
+    @classmethod
+    def rectangle(cls, picture, kind, coords):
+        pa = PictureArea()
+        pa.picture = picture
+        pa.kind = kind
+        pa.area = coords
+        return pa
+
+    def reset_short_html(self):
+        if self.id is None:
+            return
+
+        cache_key = "PictureArea.short_html/%d/%s"
+        for lang, langname in settings.LANGUAGES:
+            permanent_cache.delete(cache_key % (self.id, lang))
+
+
+    def short_html(self):
+        if self.id:
+            cache_key = "PictureArea.short_html/%d/%s" % (self.id, get_language())
+            short_html = permanent_cache.get(cache_key)
+        else:
+            short_html = None
+
+        if short_html is not None:
+            return mark_safe(short_html)
+        else:
+            theme = self.tags.filter(category='theme')
+            theme = theme and theme[0] or None
+            thing = self.tags.filter(category='thing')
+            thing = thing and thing[0] or None
+            area = self
+            short_html = unicode(render_to_string(
+                    'picture/picturearea_short.html', locals()))
+            if self.id:
+                permanent_cache.set(cache_key, short_html)
+            return mark_safe(short_html)
+
+
 class Picture(models.Model):
     """
     Picture resource.
 class Picture(models.Model):
     """
     Picture resource.
@@ -35,19 +95,24 @@ class Picture(models.Model):
     title       = models.CharField(_('title'), max_length=120)
     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)
     title       = models.CharField(_('title'), max_length=120)
     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)
     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       = jsonfield.JSONField(_('picture areas'), default={}, editable=False)
+    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)
 
     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)
     objects     = models.Manager()
     tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
     tags        = managers.TagDescriptor(catalogue.models.Tag)
+    tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     class AlreadyExists(Exception):
         pass
 
     class AlreadyExists(Exception):
         pass
@@ -99,7 +164,7 @@ 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:
             # use librarian to parse meta-data
             if image_store is None:
         try:
             # use librarian to parse meta-data
             if image_store is None:
@@ -110,11 +175,14 @@ class Picture(models.Model):
             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)
 
-            picture.title = picture_xml.picture_info.title
+            picture.areas.all().delete()
+            picture.title = unicode(picture_xml.picture_info.title)
             picture.extra_info = picture_xml.picture_info.to_dict()
 
             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':{}}
 
             for part in picture_xml.partiter():
             area_data = {'themes':{}, 'things':{}}
 
             for part in picture_xml.partiter():
@@ -128,27 +196,38 @@ class Picture(models.Model):
                         tag.name = objname
                         tag.sort_key = sortify(tag.name)
                         tag.save()
                         tag.name = objname
                         tag.sort_key = sortify(tag.name)
                         tag.save()
-                    thing_tags.add(tag)
+                    #thing_tags.add(tag)
                     area_data['things'][tag.slug] = {
                         'object': part['object'],
                         'coords': part['coords'],
                         }
                     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:
                 else:
+                    _tags = set()
                     for motif in part['themes']:
                         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()
                     for motif in part['themes']:
                         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)
+                        #motif_tags.add(tag)
+                        _tags.add(tag)
                         area_data['themes'][tag.slug] = {
                             'theme': motif,
                             'coords': part['coords']
                             }
 
                         area_data['themes'][tag.slug] = {
                             'theme': motif,
                             'coords': part['coords']
                             }
 
-            picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
-                list(motif_tags) + list(thing_tags)
-            picture.areas = area_data
+                    logging.debug("coords for theme: %s" % part['coords'])
+                    area = PictureArea.rectangle(picture, 'theme', part['coords'])
+                    area.save()
+                    area.tags = _tags.union(picture_tags)
+
+            picture.tags = picture_tags.union(motif_tags).union(thing_tags)
+            picture.areas_json = area_data
 
             if image_file is not None:
                 img = image_file
 
             if image_file is not None:
                 img = image_file
@@ -156,15 +235,21 @@ 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()
+            modified.save(modified_file, format='png', quality=95)
             # FIXME: hardcoded extension - detect from DC format or orginal filename
             # FIXME: hardcoded extension - detect from DC format or orginal filename
-            picture.image_file.save(path.basename(picture_xml.image_path), File(modified))
+            picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
 
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
             tasks.generate_picture_html(picture.id)
 
         except Exception, ex:
 
             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
             picture.save()
             tasks.generate_picture_html(picture.id)
 
         except Exception, ex:
-            print "Rolling back a transaction"
+            logging.exception("Exception during import, rolling back")
             transaction.rollback()
             raise ex
 
             transaction.rollback()
             raise ex
 
@@ -180,13 +265,31 @@ class Picture(models.Model):
 
     @classmethod
     def crop_to_frame(cls, wlpic, image_file):
 
     @classmethod
     def crop_to_frame(cls, wlpic, image_file):
-        if wlpic.frame is None:
-            return image_file
         img = Image.open(image_file)
         img = Image.open(image_file)
+        if wlpic.frame is None:
+            return img
         img = img.crop(itertools.chain(*wlpic.frame))
         img = img.crop(itertools.chain(*wlpic.frame))
-        contents = StringIO()
-        img.save(contents, format='png', quality=95)
-        return contents
+        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(img.mode, (3000, 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((1000, 40), Image.ANTIALIAS), (0, img.size[1]))
+        return annotated
 
     @classmethod
     def picture_list(cls, filter=None):
 
     @classmethod
     def picture_list(cls, filter=None):
@@ -228,13 +331,23 @@ class Picture(models.Model):
         if self.id is None:
             return
 
         if self.id is None:
             return
 
-        cache_key = "Picture.short_html/%d" % (self.id)
-        get_cache('permanent').delete(cache_key)
+        for area in self.areas.all().iterator():
+            area.reset_short_html()
+
+        try:
+            author = self.tags.filter(category='author')[0].sort_key
+        except IndexError:
+            author = u''
+        type(self).objects.filter(pk=self.pk).update(sort_key_author=author)
+
+        cache_key = "Picture.short_html/%d/%s"
+        for lang, langname in settings.LANGUAGES:
+            permanent_cache.delete(cache_key % (self.id, lang))
 
     def short_html(self):
         if self.id:
 
     def short_html(self):
         if self.id:
-            cache_key = "Picture.short_html/%d" % (self.id)
-            short_html = get_cache('permanent').get(cache_key)
+            cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
+            short_html = permanent_cache.get(cache_key)
         else:
             short_html = None
 
         else:
             short_html = None
 
@@ -249,5 +362,23 @@ class Picture(models.Model):
                     {'picture': self, 'tags': tags}))
 
             if self.id:
                     {'picture': self, 'tags': tags}))
 
             if self.id:
-                get_cache('permanent').set(cache_key, short_html)
+                permanent_cache.set(cache_key, short_html)
             return mark_safe(short_html)
             return mark_safe(short_html)
+
+    def pretty_title(self, html_links=False):
+        picture = self
+        names = [(tag.name,
+                  catalogue.models.Tag.create_url('author', tag.slug))
+                 for tag in self.tags.filter(category='author')]
+        names.append((self.title, self.get_absolute_url()))
+
+        if html_links:
+            names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
+        else:
+            names = [tag[0] for tag in names]
+        return ', '.join(names)
+
+    # copied from book.py, figure out
+    def related_themes(self):
+        return catalogue.models.Tag.usage_for_queryset(
+            self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))