1 from django.db import models, transaction
2 import catalogue.models
3 from django.db.models import permalink
4 from sorl.thumbnail import ImageField
5 from django.conf import settings
6 from django.core.files.storage import FileSystemStorage
7 from django.utils.datastructures import SortedDict
8 from django.template.loader import render_to_string
9 from django.utils.safestring import mark_safe
10 from django.core.cache import get_cache
11 from catalogue.utils import split_tags, related_tag_name
12 from django.utils.safestring import mark_safe
13 from fnpdjango.utils.text.slughifi import slughifi
14 from picture import tasks
15 from StringIO import StringIO
19 logging.basicConfig(level=logging.DEBUG)
20 from sorl.thumbnail import get_thumbnail, default
21 from .engine import CustomCroppingEngine
25 from django.utils.translation import ugettext_lazy as _
26 from newtagging import managers
30 picture_storage = FileSystemStorage(location=path.join(
31 settings.MEDIA_ROOT, 'pictures'),
32 base_url=settings.MEDIA_URL + "pictures/")
35 class PictureArea(models.Model):
36 picture = models.ForeignKey('picture.Picture', related_name='areas')
37 area = jsonfield.JSONField(_('area'), default={}, editable=False)
38 kind = models.CharField(_('kind'), max_length=10, blank=False,
39 null=False, db_index=True,
40 choices=(('thing', _('thing')),
41 ('theme', _('theme'))))
43 objects = models.Manager()
44 tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
45 tags = managers.TagDescriptor(catalogue.models.Tag)
48 def rectangle(cls, picture, kind, coords):
56 short_html = unicode(render_to_string(
57 'picture/picturearea_short.html', {'area': self}))
58 return mark_safe(short_html)
61 class Picture(models.Model):
66 title = models.CharField(_('title'), max_length=120)
67 slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
68 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
69 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
70 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
71 xml_file = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
72 image_file = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
73 html_file = models.FileField('html_file', upload_to="html", storage=picture_storage)
74 areas_json = jsonfield.JSONField(_('picture areas JSON'), default={}, editable=False)
75 extra_info = jsonfield.JSONField(_('extra information'), default={})
76 culturepl_link = models.CharField(blank=True, max_length=240)
77 wiki_link = models.CharField(blank=True, max_length=240)
79 width = models.IntegerField(null=True)
80 height = models.IntegerField(null=True)
82 objects = models.Manager()
83 tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
84 tags = managers.TagDescriptor(catalogue.models.Tag)
86 class AlreadyExists(Exception):
90 ordering = ('sort_key',)
92 verbose_name = _('picture')
93 verbose_name_plural = _('pictures')
95 def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
96 from sortify import sortify
98 self.sort_key = sortify(self.title)
100 ret = super(Picture, self).save(force_insert, force_update)
103 self.reset_short_html()
107 def __unicode__(self):
111 def get_absolute_url(self):
112 return ('picture.views.picture_detail', [self.slug])
115 def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
117 Import xml and it's accompanying image file.
118 If image file is missing, it will be fetched by librarian.picture.ImageStore
119 which looks for an image file in the same directory the xml is, with extension matching
122 from sortify import sortify
123 from django.core.files import File
124 from librarian.picture import WLPicture, ImageStore
125 close_xml_file = False
126 close_image_file = False
129 if image_file is not None and not isinstance(image_file, File):
130 image_file = File(open(image_file))
131 close_image_file = True
133 if not isinstance(xml_file, File):
134 xml_file = File(open(xml_file))
135 close_xml_file = True
138 # use librarian to parse meta-data
139 if image_store is None:
140 image_store = ImageStore(picture_storage.path('images'))
141 picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
143 picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
144 if not created and not overwrite:
145 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
147 picture.areas.all().delete()
148 picture.title = unicode(picture_xml.picture_info.title)
149 picture.extra_info = picture_xml.picture_info.to_dict()
153 area_data = {'themes':{}, 'things':{}}
155 for part in picture_xml.partiter():
156 if picture_xml.frame:
157 c = picture_xml.frame[0]
158 part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
159 if part.get('object', None) is not None:
160 objname = part['object']
161 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
164 tag.sort_key = sortify(tag.name)
167 area_data['things'][tag.slug] = {
168 'object': part['object'],
169 'coords': part['coords'],
171 area = PictureArea.rectangle(picture, 'thing', part['coords'])
178 for motif in part['themes']:
179 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
182 tag.sort_key = sortify(tag.name)
186 area_data['themes'][tag.slug] = {
188 'coords': part['coords']
191 logging.debug("coords for theme: %s" % part['coords'])
192 area = PictureArea.rectangle(picture, 'theme', part['coords'])
196 picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
197 list(motif_tags) + list(thing_tags)
198 picture.areas_json = area_data
200 if image_file is not None:
203 img = picture_xml.image_file()
205 modified = cls.crop_to_frame(picture_xml, img)
206 picture.width, picture.height = modified.size
208 modified_file = StringIO()
209 modified.save(modified_file, format='png', quality=95)
210 # FIXME: hardcoded extension - detect from DC format or orginal filename
211 picture.image_file.save(path.basename(picture_xml.image_path), File(modified_file))
213 picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
215 tasks.generate_picture_html(picture.id)
217 except Exception, ex:
218 logging.exception("Exception during import, rolling back")
219 transaction.rollback()
233 def crop_to_frame(cls, wlpic, image_file):
234 img = Image.open(image_file)
235 if wlpic.frame is None:
237 img = img.crop(itertools.chain(*wlpic.frame))
241 def picture_list(cls, filter=None):
242 """Generates a hierarchical listing of all pictures
243 Pictures are optionally filtered with a test function.
246 pics = cls.objects.all().order_by('sort_key')\
247 .only('title', 'slug', 'image_file')
250 pics = pics.filter(filter).distinct()
252 pics_by_author = SortedDict()
254 for tag in catalogue.models.Tag.objects.filter(category='author'):
255 pics_by_author[tag] = []
257 for pic in pics.iterator():
258 authors = list(pic.tags.filter(category='author'))
260 for author in authors:
261 pics_by_author[author].append(pic)
265 return pics_by_author, orphans
269 if not hasattr(self, '_info'):
270 from librarian import dcparser
271 from librarian import picture
272 info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
276 def reset_short_html(self):
280 cache_key = "Picture.short_html/%d" % (self.id)
281 get_cache('permanent').delete(cache_key)
283 def short_html(self):
285 cache_key = "Picture.short_html/%d" % (self.id)
286 short_html = get_cache('permanent').get(cache_key)
290 if short_html is not None:
291 return mark_safe(short_html)
293 tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
294 tags = split_tags(tags)
296 short_html = unicode(render_to_string(
297 'picture/picture_short.html',
298 {'picture': self, 'tags': tags}))
301 get_cache('permanent').set(cache_key, short_html)
302 return mark_safe(short_html)
304 def pretty_title(self, html_links=False):
306 # TODO Add translations (related_tag_info)
308 catalogue.models.Tag.create_url('author', tag.slug))
309 for tag in self.tags.filter(category='author')]
310 names.append((self.title, self.get_absolute_url()))
313 names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
315 names = [tag[0] for tag in names]
316 return ', '.join(names)