1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 from django.db import models
6 from django.db.models import permalink, Q
7 from django.utils.translation import ugettext_lazy as _
8 from django.contrib.auth.models import User
9 from django.core.files import File
10 from django.template.loader import render_to_string
11 from django.utils.safestring import mark_safe
12 from django.core.urlresolvers import reverse
14 from newtagging.models import TagBase
15 from newtagging import managers
16 from catalogue.fields import JSONField
18 from librarian import html, dcparser
19 from mutagen import id3
23 ('author', _('author')),
24 ('epoch', _('epoch')),
26 ('genre', _('genre')),
27 ('theme', _('theme')),
33 class TagSubcategoryManager(models.Manager):
34 def __init__(self, subcategory):
35 super(TagSubcategoryManager, self).__init__()
36 self.subcategory = subcategory
38 def get_query_set(self):
39 return super(TagSubcategoryManager, self).get_query_set().filter(category=self.subcategory)
43 name = models.CharField(_('name'), max_length=50, db_index=True)
44 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
45 sort_key = models.SlugField(_('sort key'), max_length=120, db_index=True)
46 category = models.CharField(_('category'), max_length=50, blank=False, null=False,
47 db_index=True, choices=TAG_CATEGORIES)
48 description = models.TextField(_('description'), blank=True)
49 main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page'))
51 user = models.ForeignKey(User, blank=True, null=True)
52 book_count = models.IntegerField(_('book count'), default=0, blank=False, null=False)
53 gazeta_link = models.CharField(blank=True, max_length=240)
54 wiki_link = models.CharField(blank=True, max_length=240)
56 def has_description(self):
57 return len(self.description) > 0
58 has_description.short_description = _('description')
59 has_description.boolean = True
62 def get_absolute_url(self):
63 return ('catalogue.views.tagged_object_list', [self.slug])
66 ordering = ('sort_key',)
67 verbose_name = _('tag')
68 verbose_name_plural = _('tags')
70 def __unicode__(self):
74 def get_tag_list(tags):
75 if isinstance(tags, basestring):
76 tag_slugs = tags.split('/')
77 return [Tag.objects.get(slug=slug) for slug in tag_slugs]
79 return TagBase.get_tag_list(tags)
82 def book_upload_path(ext):
83 def get_dynamic_path(book, filename):
84 return 'lektura/%s.%s' % (book.slug, ext)
85 return get_dynamic_path
88 class Book(models.Model):
89 title = models.CharField(_('title'), max_length=120)
90 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
91 description = models.TextField(_('description'), blank=True)
92 created_at = models.DateTimeField(_('creation date'), auto_now=True)
93 _short_html = models.TextField(_('short HTML'), editable=False)
94 parent_number = models.IntegerField(_('parent number'), default=0)
95 extra_info = JSONField(_('extra information'))
96 gazeta_link = models.CharField(blank=True, max_length=240)
97 wiki_link = models.CharField(blank=True, max_length=240)
101 xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
102 html_file = models.FileField(_('HTML file'), upload_to=book_upload_path('html'), blank=True)
103 pdf_file = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
104 odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), blank=True)
105 txt_file = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
106 mp3_file = models.FileField(_('MP3 file'), upload_to=book_upload_path('mp3'), blank=True)
107 ogg_file = models.FileField(_('OGG file'), upload_to=book_upload_path('ogg'), blank=True)
109 parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
111 objects = models.Manager()
112 tagged = managers.ModelTaggedItemManager(Tag)
113 tags = managers.TagDescriptor(Tag)
119 def short_html(self):
120 if len(self._short_html):
121 return mark_safe(self._short_html)
123 tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
124 tags = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)) for tag in tags]
128 formats.append(u'<a href="%s">Czytaj online</a>' % reverse('book_text', kwargs={'slug': self.slug}))
130 formats.append(u'<a href="%s">PDF</a>' % self.pdf_file.url)
132 formats.append(u'<a href="%s">ODT</a>' % self.odt_file.url)
134 formats.append(u'<a href="%s">TXT</a>' % self.txt_file.url)
136 formats.append(u'<a href="%s">MP3</a>' % self.mp3_file.url)
138 formats.append(u'<a href="%s">OGG</a>' % self.ogg_file.url)
140 formats = [mark_safe(format) for format in formats]
142 self._short_html = unicode(render_to_string('catalogue/book_short.html',
143 {'book': self, 'tags': tags, 'formats': formats}))
144 self.save(reset_short_html=False)
145 return mark_safe(self._short_html)
147 def save(self, force_insert=False, force_update=False, reset_short_html=True):
149 # Reset _short_html during save
150 self._short_html = ''
152 book = super(Book, self).save(force_insert, force_update)
155 print self.mp3_file, self.mp3_file.path
156 extra_info = self.get_extra_info_value()
157 extra_info.update(self.get_mp3_info())
158 self.set_extra_info_value(extra_info)
159 book = super(Book, self).save(force_insert, force_update)
163 def get_mp3_info(self):
164 """Retrieves artist and director names from audio ID3 tags."""
165 audio = id3.ID3(self.mp3_file.path)
166 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
167 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
168 return {'artist_name': artist_name, 'director_name': director_name}
170 def has_description(self):
171 return len(self.description) > 0
172 has_description.short_description = _('description')
173 has_description.boolean = True
175 def has_pdf_file(self):
176 return bool(self.pdf_file)
177 has_pdf_file.short_description = 'PDF'
178 has_pdf_file.boolean = True
180 def has_odt_file(self):
181 return bool(self.odt_file)
182 has_odt_file.short_description = 'ODT'
183 has_odt_file.boolean = True
185 def has_html_file(self):
186 return bool(self.html_file)
187 has_html_file.short_description = 'HTML'
188 has_html_file.boolean = True
190 class AlreadyExists(Exception):
194 def from_xml_file(xml_file, overwrite=False):
195 from tempfile import NamedTemporaryFile
196 from slughifi import slughifi
197 from markupstring import MarkupString
200 book_info = dcparser.parse(xml_file)
201 book_base, book_slug = book_info.url.rsplit('/', 1)
202 book, created = Book.objects.get_or_create(slug=book_slug)
208 raise Book.AlreadyExists('Book %s already exists' % book_slug)
209 # Save shelves for this book
210 book_shelves = list(book.tags.filter(category='set'))
212 book.title = book_info.title
213 book.set_extra_info_value(book_info.to_dict())
214 book._short_html = ''
218 for category in ('kind', 'genre', 'author', 'epoch'):
219 tag_name = getattr(book_info, category)
220 tag_sort_key = tag_name
221 if category == 'author':
222 tag_sort_key = tag_name.last_name
223 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
224 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
227 tag.sort_key = slughifi(tag_sort_key)
228 tag.category = category
230 book_tags.append(tag)
232 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
234 book_tag.name = book.title[:50]
235 book_tag.sort_key = ('l-' + book.slug)[:120]
236 book_tag.category = 'book'
238 book_tags.append(book_tag)
240 book.tags = book_tags
242 if hasattr(book_info, 'parts'):
243 for n, part_url in enumerate(book_info.parts):
244 base, slug = part_url.rsplit('/', 1)
246 child_book = Book.objects.get(slug=slug)
247 child_book.parent = book
248 child_book.parent_number = n
250 except Book.DoesNotExist, e:
251 raise Book.DoesNotExist(u'Book with slug = "%s" does not exist.' % slug)
253 book_descendants = list(book.children.all())
254 while len(book_descendants) > 0:
255 child_book = book_descendants.pop(0)
256 for fragment in child_book.fragments.all():
257 fragment.tags = set(list(fragment.tags) + [book_tag])
258 book_descendants += list(child_book.children.all())
260 # Save XML and HTML files
261 if not isinstance(xml_file, File):
262 xml_file = File(file(xml_file))
263 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
265 html_file = NamedTemporaryFile()
266 if html.transform(book.xml_file.path, html_file):
267 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
270 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
272 for fragment in closed_fragments.values():
273 text = fragment.to_string()
275 if (len(MarkupString(text)) > 240):
276 short_text = unicode(MarkupString(text)[:160])
277 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
278 defaults={'text': text, 'short_text': short_text})
281 theme_names = [s.strip() for s in fragment.themes.split(',')]
282 except AttributeError:
285 for theme_name in theme_names:
286 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
288 tag.name = theme_name
289 tag.sort_key = slughifi(theme_name)
290 tag.category = 'theme'
294 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
295 book_themes += themes
297 book_themes = set(book_themes)
298 book.tags = list(book.tags) + list(book_themes) + book_shelves
304 def get_absolute_url(self):
305 return ('catalogue.views.book_detail', [self.slug])
308 ordering = ('title',)
309 verbose_name = _('book')
310 verbose_name_plural = _('books')
312 def __unicode__(self):
316 class Fragment(models.Model):
317 text = models.TextField()
318 short_text = models.TextField(editable=False)
319 _short_html = models.TextField(editable=False)
320 anchor = models.CharField(max_length=120)
321 book = models.ForeignKey(Book, related_name='fragments')
323 objects = models.Manager()
324 tagged = managers.ModelTaggedItemManager(Tag)
325 tags = managers.TagDescriptor(Tag)
327 def short_html(self):
328 if len(self._short_html):
329 return mark_safe(self._short_html)
331 book_authors = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name))
332 for tag in self.book.tags if tag.category == 'author']
334 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
335 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
337 return mark_safe(self._short_html)
339 def get_absolute_url(self):
340 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
343 ordering = ('book', 'anchor',)
344 verbose_name = _('fragment')
345 verbose_name_plural = _('fragments')