X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/9fbf0c4158a9427442171629a564e2de7780b5b7..d2199704c75d29ba98e0f92d27cb63743183e906:/apps/picture/models.py diff --git a/apps/picture/models.py b/apps/picture/models.py index 862c172c2..2703c8091 100644 --- a/apps/picture/models.py +++ b/apps/picture/models.py @@ -1,4 +1,4 @@ -from django.db import models +from django.db import models, transaction import catalogue.models from django.db.models import permalink from sorl.thumbnail import ImageField @@ -6,17 +6,56 @@ from django.conf import settings 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 cache -from catalogue.utils import split_tags from django.utils.safestring import mark_safe -from slughifi import slughifi +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 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 newtagging import managers from os import path -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) + + @classmethod + def rectangle(cls, picture, kind, coords): + pa = PictureArea() + pa.picture = picture + pa.kind = kind + 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) class Picture(models.Model): @@ -31,6 +70,15 @@ class Picture(models.Model): 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_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) + + 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) @@ -44,9 +92,6 @@ class Picture(models.Model): verbose_name = _('picture') verbose_name_plural = _('pictures') - URLID_RE = r'[a-z0-9-]+' - FILEID_RE = r'[a-z0-9-]+' - def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs): from sortify import sortify @@ -64,13 +109,10 @@ class Picture(models.Model): @permalink def get_absolute_url(self): - return ('picture.views.picture_detail', [self.urlid()]) - - def urlid(self): - return self.slug + return ('picture.views.picture_detail', [self.slug]) @classmethod - def from_xml_file(cls, xml_file, image_file=None, overwrite=False): + def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False): """ Import xml and it's accompanying image file. If image file is missing, it will be fetched by librarian.picture.ImageStore @@ -82,10 +124,7 @@ class Picture(models.Model): from librarian.picture import WLPicture, ImageStore close_xml_file = False close_image_file = False - # class SimpleImageStore(object): - # def path(self_, slug, mime_type): - # """Returns the image file. Ignores slug ad mime_type.""" - # return image_file + if image_file is not None and not isinstance(image_file, File): image_file = File(open(image_file)) @@ -94,49 +133,110 @@ 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 - picture_xml = WLPicture.from_file(xml_file, - image_store=ImageStore(picture_storage.path('images'))) - # image_store=SimpleImageStore + 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) 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() motif_tags = set() + thing_tags = set() + area_data = {'themes':{}, 'things':{}} + for part in picture_xml.partiter(): - for motif in part['themes']: - tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme') + 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 = motif + tag.name = objname tag.sort_key = sortify(tag.name) tag.save() - motif_tags.add(tag) + #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') + 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() + area.tags = _tags picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \ - list(motif_tags) + list(motif_tags) + list(thing_tags) + picture.areas_json = area_data if image_file is not None: img = image_file else: img = picture_xml.image_file() - # FIXME: hardcoded extension - picture.image_file.save(path.basename(picture_xml.image_path), File(img)) + modified = cls.crop_to_frame(picture_xml, img) + 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 + 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: + 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() + return picture + @classmethod + def crop_to_frame(cls, wlpic, image_file): + img = Image.open(image_file) + if wlpic.frame is None: + return img + img = img.crop(itertools.chain(*wlpic.frame)) + return img + @classmethod def picture_list(cls, filter=None): """Generates a hierarchical listing of all pictures @@ -154,7 +254,7 @@ class Picture(models.Model): for tag in catalogue.models.Tag.objects.filter(category='author'): pics_by_author[tag] = [] - for pic in pics: + for pic in pics.iterator(): authors = list(pic.tags.filter(category='author')) if authors: for author in authors: @@ -178,24 +278,39 @@ class Picture(models.Model): return cache_key = "Picture.short_html/%d" % (self.id) - cache.delete(cache_key) + get_cache('permanent').delete(cache_key) def short_html(self): if self.id: cache_key = "Picture.short_html/%d" % (self.id) - short_html = cache.get(cache_key) + 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')) + 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})) + short_html = unicode(render_to_string( + 'picture/picture_short.html', + {'picture': self, 'tags': tags})) if self.id: - cache.set(cache_key, short_html, catalogue.models.CACHE_FOREVER) + 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)) + for tag in self.tags.filter(category='author')] + names.append((self.title, self.get_absolute_url())) + + if html_links: + names = ['%s' % (tag[1], tag[0]) for tag in names] + else: + names = [tag[0] for tag in names] + return ', '.join(names)