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)
44 extra_info = jsonfield.JSONField(_('extra information'), default={})
45 culturepl_link = models.CharField(blank=True, max_length=240)
46 wiki_link = models.CharField(blank=True, max_length=240)
48 objects = models.Manager()
49 tagged = managers.ModelTaggedItemManager(catalogue.models.Tag)
50 tags = managers.TagDescriptor(catalogue.models.Tag)
52 class AlreadyExists(Exception):
56 ordering = ('sort_key',)
58 verbose_name = _('picture')
59 verbose_name_plural = _('pictures')
61 def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
62 from sortify import sortify
64 self.sort_key = sortify(self.title)
66 ret = super(Picture, self).save(force_insert, force_update)
69 self.reset_short_html()
73 def __unicode__(self):
77 def get_absolute_url(self):
78 return ('picture.views.picture_detail', [self.slug])
81 def from_xml_file(cls, xml_file, image_file=None, image_store=None, overwrite=False):
83 Import xml and it's accompanying image file.
84 If image file is missing, it will be fetched by librarian.picture.ImageStore
85 which looks for an image file in the same directory the xml is, with extension matching
88 from sortify import sortify
89 from django.core.files import File
90 from librarian.picture import WLPicture, ImageStore
91 close_xml_file = False
92 close_image_file = False
95 if image_file is not None and not isinstance(image_file, File):
96 image_file = File(open(image_file))
97 close_image_file = True
99 if not isinstance(xml_file, File):
100 xml_file = File(open(xml_file))
101 close_xml_file = True
104 # use librarian to parse meta-data
105 if image_store is None:
106 image_store = ImageStore(picture_storage.path('images'))
107 picture_xml = WLPicture.from_file(xml_file, image_store=image_store)
109 picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
110 if not created and not overwrite:
111 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
113 picture.title = picture_xml.picture_info.title
114 picture.extra_info = picture_xml.picture_info.to_dict()
118 area_data = {'themes':{}, 'things':{}}
120 for part in picture_xml.partiter():
121 if picture_xml.frame:
122 c = picture_xml.frame[0]
123 part['coords'] = [[p[0] - c[0], p[1] - c[1]] for p in part['coords']]
124 if part.get('object', None) is not None:
125 objname = part['object']
126 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(objname), category='thing')
129 tag.sort_key = sortify(tag.name)
132 area_data['things'][tag.slug] = {
133 'object': part['object'],
134 'coords': part['coords'],
137 for motif in part['themes']:
138 tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
141 tag.sort_key = sortify(tag.name)
144 area_data['themes'][tag.slug] = {
146 'coords': part['coords']
149 picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
150 list(motif_tags) + list(thing_tags)
151 picture.areas = area_data
153 if image_file is not None:
156 img = picture_xml.image_file()
158 modified = cls.crop_to_frame(picture_xml, img)
159 # FIXME: hardcoded extension - detect from DC format or orginal filename
160 picture.image_file.save(path.basename(picture_xml.image_path), File(modified))
162 picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
164 tasks.generate_picture_html(picture.id)
166 except Exception, ex:
167 print "Rolling back a transaction"
168 transaction.rollback()
182 def crop_to_frame(cls, wlpic, image_file):
183 if wlpic.frame is None:
185 img = Image.open(image_file)
186 img = img.crop(itertools.chain(*wlpic.frame))
187 contents = StringIO()
188 img.save(contents, format='png', quality=95)
192 def picture_list(cls, filter=None):
193 """Generates a hierarchical listing of all pictures
194 Pictures are optionally filtered with a test function.
197 pics = cls.objects.all().order_by('sort_key')\
198 .only('title', 'slug', 'image_file')
201 pics = pics.filter(filter).distinct()
203 pics_by_author = SortedDict()
205 for tag in catalogue.models.Tag.objects.filter(category='author'):
206 pics_by_author[tag] = []
208 for pic in pics.iterator():
209 authors = list(pic.tags.filter(category='author'))
211 for author in authors:
212 pics_by_author[author].append(pic)
216 return pics_by_author, orphans
220 if not hasattr(self, '_info'):
221 from librarian import dcparser
222 from librarian import picture
223 info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
227 def reset_short_html(self):
231 cache_key = "Picture.short_html/%d" % (self.id)
232 get_cache('permanent').delete(cache_key)
234 def short_html(self):
236 cache_key = "Picture.short_html/%d" % (self.id)
237 short_html = get_cache('permanent').get(cache_key)
241 if short_html is not None:
242 return mark_safe(short_html)
244 tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
245 tags = split_tags(tags)
247 short_html = unicode(render_to_string(
248 'picture/picture_short.html',
249 {'picture': self, 'tags': tags}))
252 get_cache('permanent').set(cache_key, short_html)
253 return mark_safe(short_html)