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