minor change in import - lets not use the simpleimagestore class
[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
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=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             info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
171             self._info = info
172         return self._info
173
174     def reset_short_html(self):
175         if self.id is None:
176             return
177
178         cache_key = "Picture.short_html/%d" % (self.id)
179         cache.delete(cache_key)
180
181     def short_html(self):
182         if self.id:
183             cache_key = "Picture.short_html/%d" % (self.id)
184             short_html = cache.get(cache_key)
185         else:
186             short_html = None
187
188         if short_html is not None:
189             return mark_safe(short_html)
190         else:
191             tags = self.tags.filter(category__in=('author', 'kind', 'epoch'))
192             tags = split_tags(tags)
193
194             short_html = unicode(render_to_string('picture/picture_short.html',
195                 {'picture': self, 'tags': tags}))
196
197             if self.id:
198                 cache.set(cache_key, short_html, catalogue.models.CACHE_FOREVER)
199             return mark_safe(short_html)