Merge branch 'master' into production
[wolnelektury.git] / apps / picture / models.py
1 from django.db import models
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 slughifi import slughifi
13
14 from django.utils.translation import ugettext_lazy as _
15 from newtagging import managers
16 from os import path
17
18
19 picture_storage = FileSystemStorage(location=path.join(settings.MEDIA_ROOT, 'pictures'), base_url=settings.MEDIA_URL + "pictures/")
20
21
22 class Picture(models.Model):
23     """
24     Picture resource.
25
26     """
27     title       = models.CharField(_('title'), max_length=120)
28     slug        = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True)
29     sort_key    = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
30     created_at  = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True)
31     changed_at  = models.DateTimeField(_('creation date'), auto_now=True, db_index=True)
32     xml_file    = models.FileField('xml_file', upload_to="xml", storage=picture_storage)
33     image_file  = ImageField(_('image_file'), upload_to="images", storage=picture_storage)
34     objects     = models.Manager()
35     tagged      = managers.ModelTaggedItemManager(catalogue.models.Tag)
36     tags        = managers.TagDescriptor(catalogue.models.Tag)
37
38     class AlreadyExists(Exception):
39         pass
40
41     class Meta:
42         ordering = ('sort_key',)
43
44         verbose_name = _('picture')
45         verbose_name_plural = _('pictures')
46
47     def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
48         from sortify import sortify
49
50         self.sort_key = sortify(self.title)
51
52         ret = super(Picture, self).save(force_insert, force_update)
53
54         if reset_short_html:
55             self.reset_short_html()
56
57         return ret
58
59     def __unicode__(self):
60         return self.title
61
62     @permalink
63     def get_absolute_url(self):
64         return ('picture.views.picture_detail', [self.slug])
65
66     @classmethod
67     def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
68         """
69         Import xml and it's accompanying image file.
70         If image file is missing, it will be fetched by librarian.picture.ImageStore
71         which looks for an image file in the same directory the xml is, with extension matching
72         its mime type.
73         """
74         from sortify import sortify
75         from django.core.files import File
76         from librarian.picture import WLPicture, ImageStore
77         close_xml_file = False
78         close_image_file = False
79         # class SimpleImageStore(object):
80         #     def path(self_, slug, mime_type):
81         #         """Returns the image file. Ignores slug ad mime_type."""
82         #         return image_file
83
84         if image_file is not None and not isinstance(image_file, File):
85             image_file = File(open(image_file))
86             close_image_file = True
87
88         if not isinstance(xml_file, File):
89             xml_file = File(open(xml_file))
90             close_xml_file = True
91
92         try:
93             # use librarian to parse meta-data
94             picture_xml = WLPicture.from_file(xml_file,
95                                               image_store=ImageStore(picture_storage.path('images')))
96                     # image_store=SimpleImageStore
97
98             picture, created = Picture.objects.get_or_create(slug=picture_xml.slug)
99             if not created and not overwrite:
100                 raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug)
101
102             picture.title = picture_xml.picture_info.title
103
104             motif_tags = set()
105             for part in picture_xml.partiter():
106                 for motif in part['themes']:
107                     tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme')
108                     if created:
109                         tag.name = motif
110                         tag.sort_key = sortify(tag.name)
111                         tag.save()
112                     motif_tags.add(tag)
113
114             picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \
115                 list(motif_tags)
116
117             if image_file is not None:
118                 img = image_file
119             else:
120                 img = picture_xml.image_file()
121
122             # FIXME: hardcoded extension
123             picture.image_file.save(path.basename(picture_xml.image_path), File(img))
124
125             picture.xml_file.save("%s.xml" % picture.slug, File(xml_file))
126             picture.save()
127         finally:
128             if close_xml_file:
129                 xml_file.close()
130             if close_image_file:
131                 image_file.close()
132         return picture
133
134     @classmethod
135     def picture_list(cls, filter=None):
136         """Generates a hierarchical listing of all pictures
137         Pictures are optionally filtered with a test function.
138         """
139
140         pics = cls.objects.all().order_by('sort_key')\
141             .only('title', 'slug', 'image_file')
142
143         if filter:
144             pics = pics.filter(filter).distinct()
145
146         pics_by_author = SortedDict()
147         orphans = []
148         for tag in catalogue.models.Tag.objects.filter(category='author'):
149             pics_by_author[tag] = []
150
151         for pic in pics.iterator():
152             authors = list(pic.tags.filter(category='author'))
153             if authors:
154                 for author in authors:
155                     pics_by_author[author].append(pic)
156             else:
157                 orphans.append(pic)
158
159         return pics_by_author, orphans
160
161     @property
162     def info(self):
163         if not hasattr(self, '_info'):
164             from librarian import dcparser
165             from librarian import picture
166             info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
167             self._info = info
168         return self._info
169
170     def reset_short_html(self):
171         if self.id is None:
172             return
173
174         cache_key = "Picture.short_html/%d" % (self.id)
175         get_cache('permanent').delete(cache_key)
176
177     def short_html(self):
178         if self.id:
179             cache_key = "Picture.short_html/%d" % (self.id)
180             short_html = get_cache('permanent').get(cache_key)
181         else:
182             short_html = None
183
184         if short_html is not None:
185             return mark_safe(short_html)
186         else:
187             tags = self.tags.filter(category__in=('author', 'kind', 'epoch'))
188             tags = split_tags(tags)
189
190             short_html = unicode(render_to_string('picture/picture_short.html',
191                 {'picture': self, 'tags': tags}))
192
193             if self.id:
194                 get_cache('permanent').set(cache_key, short_html)
195             return mark_safe(short_html)