1 # -*- coding: utf-8 -*-
2 from django.db import models
3 from django.db.models import permalink, Q
4 from django.utils.translation import ugettext_lazy as _
5 from django.contrib.auth.models import User
6 from django.core.files import File
7 from django.template.loader import render_to_string
8 from django.utils.safestring import mark_safe
9 from django.core.urlresolvers import reverse
11 from newtagging.models import TagBase
12 from newtagging import managers
13 from catalogue.fields import JSONField
15 from librarian import html, dcparser
16 from mutagen import id3
20 ('author', _('author')),
21 ('epoch', _('epoch')),
23 ('genre', _('genre')),
24 ('theme', _('theme')),
30 class TagSubcategoryManager(models.Manager):
31 def __init__(self, subcategory):
32 super(TagSubcategoryManager, self).__init__()
33 self.subcategory = subcategory
35 def get_query_set(self):
36 return super(TagSubcategoryManager, self).get_query_set().filter(category=self.subcategory)
40 name = models.CharField(_('name'), max_length=50, db_index=True)
41 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
42 sort_key = models.SlugField(_('sort key'), max_length=120, db_index=True)
43 category = models.CharField(_('category'), max_length=50, blank=False, null=False,
44 db_index=True, choices=TAG_CATEGORIES)
45 description = models.TextField(_('description'), blank=True)
46 main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page'))
48 user = models.ForeignKey(User, blank=True, null=True)
49 book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False)
50 gazeta_link = models.CharField(blank=True, max_length=240)
51 wiki_link = models.CharField(blank=True, max_length=240)
53 def has_description(self):
54 return len(self.description) > 0
55 has_description.short_description = _('description')
56 has_description.boolean = True
59 def get_absolute_url(self):
60 return ('catalogue.views.tagged_object_list', [self.slug])
63 ordering = ('sort_key',)
64 verbose_name = _('tag')
65 verbose_name_plural = _('tags')
67 def __unicode__(self):
71 def get_tag_list(tags):
72 if isinstance(tags, basestring):
73 tag_slugs = tags.split('/')
74 return [Tag.objects.get(slug=slug) for slug in tag_slugs]
76 return TagBase.get_tag_list(tags)
79 def book_upload_path(ext):
80 def get_dynamic_path(book, filename):
81 return 'lektura/%s.%s' % (book.slug, ext)
82 return get_dynamic_path
85 class Book(models.Model):
86 title = models.CharField(_('title'), max_length=120)
87 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
88 description = models.TextField(_('description'), blank=True)
89 created_at = models.DateTimeField(_('creation date'), auto_now=True)
90 _short_html = models.TextField(_('short HTML'), editable=False)
91 parent_number = models.IntegerField(_('parent number'), default=0)
92 extra_info = JSONField(_('extra information'))
93 gazeta_link = models.CharField(blank=True, max_length=240)
94 wiki_link = models.CharField(blank=True, max_length=240)
98 xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
99 html_file = models.FileField(_('HTML file'), upload_to=book_upload_path('html'), blank=True)
100 pdf_file = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
101 odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), blank=True)
102 txt_file = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
103 mp3_file = models.FileField(_('MP3 file'), upload_to=book_upload_path('mp3'), blank=True)
104 ogg_file = models.FileField(_('OGG file'), upload_to=book_upload_path('ogg'), blank=True)
106 parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
108 objects = models.Manager()
109 tagged = managers.ModelTaggedItemManager(Tag)
110 tags = managers.TagDescriptor(Tag)
117 def short_html(self):
118 if len(self._short_html):
119 return mark_safe(self._short_html)
121 tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
122 tags = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)) for tag in tags]
126 formats.append(u'<a href="%s">Czytaj online</a>' % reverse('book_text', kwargs={'slug': self.slug}))
128 formats.append(u'<a href="%s">PDF</a>' % self.pdf_file.url)
130 formats.append(u'<a href="%s">ODT</a>' % self.odt_file.url)
132 formats.append(u'<a href="%s">TXT</a>' % self.txt_file.url)
134 formats.append(u'<a href="%s">MP3</a>' % self.mp3_file.url)
136 formats.append(u'<a href="%s">OGG</a>' % self.ogg_file.url)
138 formats = [mark_safe(format) for format in formats]
140 self._short_html = unicode(render_to_string('catalogue/book_short.html',
141 {'book': self, 'tags': tags, 'formats': formats}))
142 self.save(reset_short_html=False)
143 return mark_safe(self._short_html)
145 def save(self, force_insert=False, force_update=False, reset_short_html=True):
147 # Reset _short_html during save
148 self._short_html = ''
150 book = super(Book, self).save(force_insert, force_update)
153 print self.mp3_file, self.mp3_file.path
154 extra_info = self.get_extra_info_value()
155 extra_info.update(self.get_mp3_info())
156 self.set_extra_info_value(extra_info)
157 book = super(Book, self).save(force_insert, force_update)
161 def get_mp3_info(self):
162 """Retrieves artist and director names from audio ID3 tags."""
163 audio = id3.ID3(self.mp3_file.path)
164 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
165 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
166 return {'artist_name': artist_name, 'director_name': director_name}
168 def has_description(self):
169 return len(self.description) > 0
170 has_description.short_description = _('description')
171 has_description.boolean = True
173 def has_pdf_file(self):
174 return bool(self.pdf_file)
175 has_pdf_file.short_description = 'PDF'
176 has_pdf_file.boolean = True
178 def has_odt_file(self):
179 return bool(self.odt_file)
180 has_odt_file.short_description = 'ODT'
181 has_odt_file.boolean = True
183 def has_html_file(self):
184 return bool(self.html_file)
185 has_html_file.short_description = 'HTML'
186 has_html_file.boolean = True
188 class AlreadyExists(Exception):
192 def from_xml_file(xml_file, overwrite=False):
193 from tempfile import NamedTemporaryFile
194 from slughifi import slughifi
195 from markupstring import MarkupString
198 book_info = dcparser.parse(xml_file)
199 book_base, book_slug = book_info.url.rsplit('/', 1)
200 book, created = Book.objects.get_or_create(slug=book_slug)
206 raise Book.AlreadyExists('Book %s already exists' % book_slug)
207 # Save shelves for this book
208 book_shelves = list(book.tags.filter(category='set'))
210 book.title = book_info.title
211 book.set_extra_info_value(book_info.to_dict())
212 book._short_html = ''
216 for category in ('kind', 'genre', 'author', 'epoch'):
217 tag_name = getattr(book_info, category)
218 tag_sort_key = tag_name
219 if category == 'author':
220 tag_sort_key = tag_name.last_name
221 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
222 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
225 tag.sort_key = slughifi(tag_sort_key)
226 tag.category = category
228 book_tags.append(tag)
230 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
232 book_tag.name = book.title[:50]
233 book_tag.sort_key = ('l-' + book.slug)[:120]
234 book_tag.category = 'book'
236 book_tags.append(book_tag)
238 book.tags = book_tags
240 if hasattr(book_info, 'parts'):
241 for n, part_url in enumerate(book_info.parts):
242 base, slug = part_url.rsplit('/', 1)
244 child_book = Book.objects.get(slug=slug)
245 child_book.parent = book
246 child_book.parent_number = n
248 except Book.DoesNotExist, e:
249 raise Book.DoesNotExist(u'Book with slug = "%s" does not exist.' % slug)
251 book_descendants = list(book.children.all())
252 while len(book_descendants) > 0:
253 child_book = book_descendants.pop(0)
254 for fragment in child_book.fragments.all():
255 fragment.tags = set(list(fragment.tags) + [book_tag])
256 book_descendants += list(child_book.children.all())
258 # Save XML and HTML files
259 if not isinstance(xml_file, File):
260 xml_file = File(file(xml_file))
261 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
263 html_file = NamedTemporaryFile()
264 if html.transform(book.xml_file.path, html_file):
265 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
268 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
270 for fragment in closed_fragments.values():
271 text = fragment.to_string()
273 if (len(MarkupString(text)) > 240):
274 short_text = unicode(MarkupString(text)[:160])
275 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
276 defaults={'text': text, 'short_text': short_text})
279 theme_names = [s.strip() for s in fragment.themes.split(',')]
280 except AttributeError:
283 for theme_name in theme_names:
284 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
286 tag.name = theme_name
287 tag.sort_key = slughifi(theme_name)
288 tag.category = 'theme'
292 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
293 book_themes += themes
295 book_themes = set(book_themes)
296 book.tags = list(book.tags) + list(book_themes) + book_shelves
302 def get_absolute_url(self):
303 return ('catalogue.views.book_detail', [self.slug])
306 ordering = ('title',)
307 verbose_name = _('book')
308 verbose_name_plural = _('books')
310 def __unicode__(self):
314 class Fragment(models.Model):
315 text = models.TextField()
316 short_text = models.TextField(editable=False)
317 _short_html = models.TextField(editable=False)
318 anchor = models.CharField(max_length=120)
319 book = models.ForeignKey(Book, related_name='fragments')
321 objects = models.Manager()
322 tagged = managers.ModelTaggedItemManager(Tag)
323 tags = managers.TagDescriptor(Tag)
325 def short_html(self):
326 if len(self._short_html):
327 return mark_safe(self._short_html)
329 book_authors = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name))
330 for tag in self.book.tags if tag.category == 'author']
332 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
333 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
335 return mark_safe(self._short_html)
337 def get_absolute_url(self):
338 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
341 ordering = ('book', 'anchor',)
342 verbose_name = _('fragment')
343 verbose_name_plural = _('fragments')