X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/d2199704c75d29ba98e0f92d27cb63743183e906..5158c6e72fb24c87dd8c42a8d171222f1ee1d803:/apps/picture/models.py diff --git a/apps/picture/models.py b/apps/picture/models.py index 2703c8091..2707e088e 100644 --- a/apps/picture/models.py +++ b/apps/picture/models.py @@ -1,3 +1,7 @@ +# -*- 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 @@ -8,25 +12,23 @@ 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 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 -logging.basicConfig(level=logging.DEBUG) -from sorl.thumbnail import get_thumbnail, default -from .engine import CustomCroppingEngine 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 +permanent_cache = get_cache('permanent') + picture_storage = FileSystemStorage(location=path.join( settings.MEDIA_ROOT, 'pictures'), base_url=settings.MEDIA_URL + "pictures/") @@ -35,9 +37,9 @@ 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) - 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() @@ -52,10 +54,35 @@ class PictureArea(models.Model): 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): - short_html = unicode(render_to_string( - 'picture/picturearea_short.html', {'area': self})) - return mark_safe(short_html) + 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): @@ -66,6 +93,7 @@ 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) + 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) @@ -76,6 +104,8 @@ class Picture(models.Model): 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) @@ -133,7 +163,7 @@ class Picture(models.Model): 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: @@ -148,8 +178,10 @@ class Picture(models.Model): 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() + area_data = {'themes':{}, 'things':{}} for part in picture_xml.partiter(): @@ -191,10 +223,9 @@ class Picture(models.Model): 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: @@ -203,6 +234,8 @@ class Picture(models.Model): 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() @@ -237,6 +270,26 @@ class Picture(models.Model): 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 @@ -277,12 +330,23 @@ class Picture(models.Model): if self.id is None: return - cache_key = "Picture.short_html/%d" % (self.id) - get_cache('permanent').delete(cache_key) + type(self).objects.filter(pk=self.pk).update(_related_info=None) + 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: - cache_key = "Picture.short_html/%d" % (self.id) + cache_key = "Picture.short_html/%d/%s" % (self.id, get_language()) short_html = get_cache('permanent').get(cache_key) else: short_html = None @@ -314,3 +378,90 @@ class Picture(models.Model): else: 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 + 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