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.core.cache import get_cache
10 from catalogue.utils import split_tags
11 from django.utils.safestring import mark_safe
12 from fnpdjango.utils.text.slughifi import slughifi
13 from picture import tasks
14 from StringIO import StringIO
20 from django.utils.translation import ugettext_lazy as _
21 from newtagging import managers
25 picture_storage = FileSystemStorage(location=path.join(
26 settings.MEDIA_ROOT, 'pictures'),
27 base_url=settings.MEDIA_URL + "pictures/")
30 class Picture(models.Model):
35 title = models.CharField(_('title'), max_length=120)
36 slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
37 sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
38 created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
39 changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
40 xml_file = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
41 image_file = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
42 html_file = models.FileField('html_file', upload_to="html", storage=picture_storage)
43 areas = jsonfield.JSONField(_('picture areas'), default={}, editable=False)
45 objects = models.Manager()
46 tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
47 tags = managers.TagDescriptor(catalogue.models.Tag)
49 class AlreadyExists(Exception):
53 ordering = ('sort_key',)
55 verbose_name = _('picture')
56 verbose_name_plural = _('pictures')
58 def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
59 from sortify import sortify
61 self.sort_key = sortify(self.title)
63 ret = super(Picture, self).save(force_insert, force_update)
66 self.reset_short_html()
70 def __unicode__(self):
74 def get_absolute_url(self):
75 return ('picture.views.picture_detail', [self.slug])
78 def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
80 Import xml and it's accompanying image file.
81 If image file is missing, it will be fetched by librarian.picture.ImageStore
82 which looks for an image file in the same directory the xml is, with extension matching
85 from sortify import sortify
86 from django.core.files import File
87 from librarian.picture import WLPicture, ImageStore
88 close_xml_file = False
89 close_image_file = False
92 if image_file is not None and not isinstance(image_file, File):
93 image_file = File(open(image_file))
94 close_image_file = True
96 if not isinstance(xml_file, File):
97 xml_file = File(open(xml_file))
101 # use librarian to parse meta-data
102 if image_store is None:
103 image_store = ImageStore(picture_storage.path('images'))
104 picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
106 picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
107 if not created and not overwrite:
108 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
110 picture.title = picture_xml.picture_info.title
114 area_data = {'themes':{}, 'things':{}}
116 for part in picture_xml.partiter():
117 if picture_xml.frame:
118 c = picture_xml.frame[0]
119 part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
120 if part.get('object', None) is not None:
121 objname = part['object']
122 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
125 tag.sort_key = sortify(tag.name)
128 area_data['things'][tag.slug] = {
129 'object': part['object'],
130 'coords': part['coords'],
133 for motif in part['themes']:
134 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
137 tag.sort_key = sortify(tag.name)
140 area_data['themes'][tag.slug] = {
142 'coords': part['coords']
145 picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
146 list(motif_tags) + list(thing_tags)
147 picture.areas = area_data
149 if image_file is not None:
152 img = picture_xml.image_file()
154 modified = cls.crop_to_frame(picture_xml, img)
155 # FIXME: hardcoded extension - detect from DC format or orginal filename
156 picture.image_file.save(path.basename(picture_xml.image_path), File(modified))
158 picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
160 tasks.generate_picture_html(picture.id)
162 except Exception, ex:
163 print "Rolling back a transaction"
164 transaction.rollback()
178 def crop_to_frame(cls, wlpic, image_file):
179 if wlpic.frame is None:
181 img = Image.open(image_file)
182 img = img.crop(itertools.chain(*wlpic.frame))
183 contents = StringIO()
184 img.save(contents, format='png', quality=95)
188 def picture_list(cls, filter=None):
189 """Generates a hierarchical listing of all pictures
190 Pictures are optionally filtered with a test function.
193 pics = cls.objects.all().order_by('sort_key')\
194 .only('title', 'slug', 'image_file')
197 pics = pics.filter(filter).distinct()
199 pics_by_author = SortedDict()
201 for tag in catalogue.models.Tag.objects.filter(category='author'):
202 pics_by_author[tag] = []
204 for pic in pics.iterator():
205 authors = list(pic.tags.filter(category='author'))
207 for author in authors:
208 pics_by_author[author].append(pic)
212 return pics_by_author, orphans
216 if not hasattr(self, '_info'):
217 from librarian import dcparser
218 from librarian import picture
219 info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
223 def reset_short_html(self):
227 cache_key = "Picture.short_html/%d" % (self.id)
228 get_cache('permanent').delete(cache_key)
230 def short_html(self):
232 cache_key = "Picture.short_html/%d" % (self.id)
233 short_html = get_cache('permanent').get(cache_key)
237 if short_html is not None:
238 return mark_safe(short_html)
240 tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
241 tags = split_tags(tags)
243 short_html = unicode(render_to_string(
244 'picture/picture_short.html',
245 {'picture': self, 'tags': tags}))
248 get_cache('permanent').set(cache_key, short_html)
249 return mark_safe(short_html)