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