X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/08bd2b6b6557481ecbb64b2f07db0d3d9784d9bb..4bb3e264dc9d9953c6a1ffcb0290a2ba0e0c094b:/apps/picture/models.py diff --git a/apps/picture/models.py b/apps/picture/models.py index 705025a7f..ee2b4e001 100644 --- a/apps/picture/models.py +++ b/apps/picture/models.py @@ -1,33 +1,30 @@ +# -*- 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.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.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 ssify import flush_ssi_includes 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 django.utils.translation import get_language, ugettext_lazy as _ +from django.utils.translation import 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/") @@ -36,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) - 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) + tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model) + + short_html_url_name = 'picture_area_short' @classmethod def rectangle(cls, picture, kind, coords): @@ -53,35 +53,18 @@ class PictureArea(models.Model): pa.area = coords return pa - def reset_short_html(self): - if self.id is None: + def flush_includes(self, languages=True): + if not languages: 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) + 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): @@ -89,7 +72,7 @@ class Picture(models.Model): 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) sort_key_author = models.CharField(_('sort key by author'), max_length=120, db_index=True, editable=False, default=u'') @@ -103,14 +86,15 @@ 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) 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 @@ -121,15 +105,18 @@ class Picture(models.Model): 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 - 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 @@ -162,14 +149,14 @@ 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: 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) @@ -183,42 +170,49 @@ class Picture(models.Model): 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: - 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() - 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: - tag.name = motif + tag.name = objname + setattr(tag, 'name_%s' % lang, tag.name) 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() @@ -233,10 +227,12 @@ 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() - 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)) @@ -262,11 +258,31 @@ class Picture(models.Model): @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 + @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 @@ -303,50 +319,9 @@ class Picture(models.Model): self._info = info return self._info - 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() - - 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/%s" % (self.id, get_language()) - 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 - # 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())) @@ -356,89 +331,20 @@ class Picture(models.Model): 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 + return catalogue.models.Tag.objects.usage_for_queryset( + self.areas.all(), counts=True).filter(category__in=('theme', 'thing')) - if self.id: - permanent_cache.set(cache_key, tags) - return tags - - def reset_theme_counter(self): - if self.id is None: + def flush_includes(self, languages=True): + if not languages: 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 + 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 + ])