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}))
143 return mark_safe(self._short_html)
145 def save(self, force_insert=False, force_update=False):
147 extra_info = self.get_extra_info_value()
148 extra_info.update(self.get_mp3_info())
149 self.set_extra_info_value(extra_info)
150 return super(Book, self).save(force_insert, force_update)
152 def get_mp3_info(self):
153 """Retrieves artist and director names from audio ID3 tags."""
154 audio = id3.ID3(self.mp3_file.path)
155 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
156 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
157 return {'artist_name': artist_name, 'director_name': director_name}
159 def has_description(self):
160 return len(self.description) > 0
161 has_description.short_description = _('description')
162 has_description.boolean = True
164 def has_pdf_file(self):
165 return bool(self.pdf_file)
166 has_pdf_file.short_description = 'PDF'
167 has_pdf_file.boolean = True
169 def has_odt_file(self):
170 return bool(self.odt_file)
171 has_odt_file.short_description = 'ODT'
172 has_odt_file.boolean = True
174 def has_html_file(self):
175 return bool(self.html_file)
176 has_html_file.short_description = 'HTML'
177 has_html_file.boolean = True
179 class AlreadyExists(Exception):
183 def from_xml_file(xml_file, overwrite=False):
184 from tempfile import NamedTemporaryFile
185 from slughifi import slughifi
186 from markupstring import MarkupString
189 book_info = dcparser.parse(xml_file)
190 book_base, book_slug = book_info.url.rsplit('/', 1)
191 book, created = Book.objects.get_or_create(slug=book_slug)
197 raise Book.AlreadyExists('Book %s already exists' % book_slug)
198 # Save shelves for this book
199 book_shelves = list(book.tags.filter(category='set'))
201 book.title = book_info.title
202 book.set_extra_info_value(book_info.to_dict())
203 book._short_html = ''
207 for category in ('kind', 'genre', 'author', 'epoch'):
208 tag_name = getattr(book_info, category)
209 tag_sort_key = tag_name
210 if category == 'author':
211 tag_sort_key = tag_name.last_name
212 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
213 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
216 tag.sort_key = slughifi(tag_sort_key)
217 tag.category = category
219 book_tags.append(tag)
221 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
223 book_tag.name = book.title[:50]
224 book_tag.sort_key = ('l-' + book.slug)[:120]
225 book_tag.category = 'book'
227 book_tags.append(book_tag)
229 book.tags = book_tags
231 if hasattr(book_info, 'parts'):
232 for n, part_url in enumerate(book_info.parts):
233 base, slug = part_url.rsplit('/', 1)
234 child_book = Book.objects.get(slug=slug)
235 child_book.parent = book
236 child_book.parent_number = n
239 book_descendants = list(book.children.all())
240 while len(book_descendants) > 0:
241 child_book = book_descendants.pop(0)
242 for fragment in child_book.fragments.all():
243 fragment.tags = set(list(fragment.tags) + [book_tag])
244 book_descendants += list(child_book.children.all())
246 # Save XML and HTML files
247 if not isinstance(xml_file, File):
248 xml_file = File(file(xml_file))
249 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
251 html_file = NamedTemporaryFile()
252 if html.transform(book.xml_file.path, html_file):
253 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
256 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
258 for fragment in closed_fragments.values():
259 text = fragment.to_string()
261 if (len(MarkupString(text)) > 240):
262 short_text = unicode(MarkupString(text)[:160])
263 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
264 defaults={'text': text, 'short_text': short_text})
267 theme_names = [s.strip() for s in fragment.themes.split(',')]
268 except AttributeError:
271 for theme_name in theme_names:
272 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
274 tag.name = theme_name
275 tag.sort_key = slughifi(theme_name)
276 tag.category = 'theme'
280 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
281 book_themes += themes
283 book_themes = set(book_themes)
284 book.tags = list(book.tags) + list(book_themes) + book_shelves
290 def get_absolute_url(self):
291 return ('catalogue.views.book_detail', [self.slug])
294 ordering = ('title',)
295 verbose_name = _('book')
296 verbose_name_plural = _('books')
298 def __unicode__(self):
302 class Fragment(models.Model):
303 text = models.TextField()
304 short_text = models.TextField(editable=False)
305 _short_html = models.TextField(editable=False)
306 anchor = models.CharField(max_length=120)
307 book = models.ForeignKey(Book, related_name='fragments')
309 objects = models.Manager()
310 tagged = managers.ModelTaggedItemManager(Tag)
311 tags = managers.TagDescriptor(Tag)
313 def short_html(self):
314 if len(self._short_html):
315 return mark_safe(self._short_html)
317 book_authors = [mark_safe(u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name))
318 for tag in self.book.tags if tag.category == 'author']
320 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
321 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
323 return mark_safe(self._short_html)
325 def get_absolute_url(self):
326 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
329 ordering = ('book', 'anchor',)
330 verbose_name = _('fragment')
331 verbose_name_plural = _('fragments')