Update NOTICE.
[wolnelektury.git] / apps / picture / models.py
index 705025a..5fc3722 100644 (file)
@@ -1,23 +1,25 @@
+# -*- 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.utils.safestring import mark_safe
 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 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
 import logging
 from fnpdjango.utils.text.slughifi import slughifi
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 import itertools
 import logging
-from sorl.thumbnail import get_thumbnail, default
-from .engine import CustomCroppingEngine
 
 from PIL import Image
 
 
 from PIL import Image
 
@@ -26,7 +28,7 @@ from newtagging import managers
 from os import path
 
 
 from os import path
 
 
-permanent_cache = get_cache('permanent')
+permanent_cache = caches['permanent']
 
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
 
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
@@ -36,14 +38,15 @@ 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)
 
     @classmethod
     def rectangle(cls, picture, kind, coords):
 
     @classmethod
     def rectangle(cls, picture, kind, coords):
@@ -68,7 +71,7 @@ class PictureArea(models.Model):
             short_html = permanent_cache.get(cache_key)
         else:
             short_html = None
             short_html = permanent_cache.get(cache_key)
         else:
             short_html = None
-    
+
         if short_html is not None:
             return mark_safe(short_html)
         else:
         if short_html is not None:
             return mark_safe(short_html)
         else:
@@ -103,14 +106,13 @@ class Picture(models.Model):
     culturepl_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
 
     culturepl_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
 
-    _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)
-
     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)
     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)
 
     class AlreadyExists(Exception):
         pass
 
     class AlreadyExists(Exception):
         pass
@@ -162,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:
@@ -233,6 +235,8 @@ 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()
@@ -267,6 +271,26 @@ class Picture(models.Model):
         img = img.crop(itertools.chain(*wlpic.frame))
         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(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):
         """Generates a hierarchical listing of all pictures
     @classmethod
     def picture_list(cls, filter=None):
         """Generates a hierarchical listing of all pictures
@@ -306,12 +330,11 @@ class Picture(models.Model):
     def reset_short_html(self):
         if self.id is None:
             return
     def reset_short_html(self):
         if self.id is None:
             return
-        
-        type(self).objects.filter(pk=self.pk).update(_related_info=None)
+
         for area in self.areas.all().iterator():
             area.reset_short_html()
 
         for area in self.areas.all().iterator():
             area.reset_short_html()
 
-        try: 
+        try:
             author = self.tags.filter(category='author')[0].sort_key
         except IndexError:
             author = u''
             author = self.tags.filter(category='author')[0].sort_key
         except IndexError:
             author = u''
@@ -324,7 +347,7 @@ class Picture(models.Model):
     def short_html(self):
         if self.id:
             cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
     def short_html(self):
         if self.id:
             cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
-            short_html = get_cache('permanent').get(cache_key)
+            short_html = permanent_cache.get(cache_key)
         else:
             short_html = None
 
         else:
             short_html = None
 
@@ -339,12 +362,11 @@ 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)
 
     def pretty_title(self, html_links=False):
         picture = self
             return mark_safe(short_html)
 
     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))
                  for tag in self.tags.filter(category='author')]
         names = [(tag.name,
                   catalogue.models.Tag.create_url('author', tag.slug))
                  for tag in self.tags.filter(category='author')]
@@ -356,89 +378,7 @@ class Picture(models.Model):
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
-    def related_info(self):
-        """Keeps info about related objects (tags) in cache field."""
-        if self._related_info is not None:
-            return self._related_info
-        else:
-            rel = {'tags': {}}
-
-            tags = self.tags.filter(category__in=(
-                    'author', 'kind', 'genre', 'epoch'))
-            tags = split_tags(tags)
-            for category in tags:
-                cat = []
-                for tag in tags[category]:
-                    tag_info = {'slug': tag.slug, 'name': tag.name}
-                    for lc, ln in settings.LANGUAGES:
-                        tag_name = getattr(tag, "name_%s" % lc)
-                        if tag_name:
-                            tag_info["name_%s" % lc] = tag_name
-                    cat.append(tag_info)
-                rel['tags'][category] = cat
-            
-
-            if self.pk:
-                type(self).objects.filter(pk=self.pk).update(_related_info=rel)
-            return rel
-
-    # copied from book.py, figure out 
+    # copied from book.py, figure out
     def related_themes(self):
     def related_themes(self):
-        # self.theme_counter hides a computation, so a line below actually makes sense
-        theme_counter = self.theme_counter
-        picture_themes = list(catalogue.models.Tag.objects.filter(pk__in=theme_counter.keys()))
-        for tag in picture_themes:
-            tag.count = theme_counter[tag.pk]
-        return picture_themes
-
-    def reset_tag_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Picture.tag_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-        if self.parent:
-            self.parent.reset_tag_counter()
-
-    @property
-    def tag_counter(self):
-        if self.id:
-            cache_key = "Picture.tag_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            # do we need to do this? there are no children here.
-            for tag in self.tags.exclude(category__in=('book', 'theme', 'thing', 'set')).order_by().iterator():
-                tags[tag.pk] = 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
-
-    def reset_theme_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Picture.theme_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-
-    @property
-    def theme_counter(self):
-        if self.id:
-            cache_key = "Picture.theme_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            for area in PictureArea.objects.filter(picture=self).order_by().iterator():
-                for tag in area.tags.filter(category__in=('theme','thing')).order_by().iterator():
-                    tags[tag.pk] = tags.get(tag.pk, 0) + 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
+        return catalogue.models.Tag.objects.usage_for_queryset(
+            self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))