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)
51 def has_description(self):
52 return len(self.description) > 0
53 has_description.short_description = _('description')
54 has_description.boolean = True
57 def get_absolute_url(self):
58 return ('catalogue.views.tagged_object_list', [self.slug])
61 ordering = ('sort_key',)
62 verbose_name = _('tag')
63 verbose_name_plural = _('tags')
65 def __unicode__(self):
69 def get_tag_list(tags):
70 if isinstance(tags, basestring):
71 tag_slugs = tags.split('/')
72 return [Tag.objects.get(slug=slug) for slug in tag_slugs]
74 return TagBase.get_tag_list(tags)
77 def book_upload_path(ext):
78 def get_dynamic_path(book, filename):
79 return 'lektura/%s.%s' % (book.slug, ext)
80 return get_dynamic_path
83 class Book(models.Model):
84 title = models.CharField(_('title'), max_length=120)
85 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
86 description = models.TextField(_('description'), blank=True)
87 created_at = models.DateTimeField(_('creation date'), auto_now=True)
88 _short_html = models.TextField(_('short HTML'), editable=False)
89 parent_number = models.IntegerField(_('parent number'), default=0)
90 extra_info = JSONField(_('extra information'))
93 xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
94 html_file = models.FileField(_('HTML file'), upload_to=book_upload_path('html'), blank=True)
95 pdf_file = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
96 odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), blank=True)
97 txt_file = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
98 mp3_file = models.FileField(_('MP3 file'), upload_to=book_upload_path('mp3'), blank=True)
99 ogg_file = models.FileField(_('OGG file'), upload_to=book_upload_path('ogg'), blank=True)
101 parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
103 objects = models.Manager()
104 tagged = managers.ModelTaggedItemManager(Tag)
105 tags = managers.TagDescriptor(Tag)
112 def short_html(self):
113 if len(self._short_html):
114 return mark_safe(self._short_html)
116 tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
117 tags = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in tags]
121 formats.append(u'<a href="%s">Czytaj online</a>' % reverse('book_text', kwargs={'slug': self.slug}))
123 formats.append(u'<a href="%s">Plik PDF</a>' % self.pdf_file.url)
125 formats.append(u'<a href="%s">Plik ODT</a>' % self.odt_file.url)
127 formats.append(u'<a href="%s">Plik TXT</a>' % self.txt_file.url)
129 self._short_html = unicode(render_to_string('catalogue/book_short.html',
130 {'book': self, 'tags': tags, 'formats': formats}))
132 return mark_safe(self._short_html)
134 def save(self, force_insert=False, force_update=False):
136 extra_info = self.get_extra_info_value()
137 extra_info.update(self.get_mp3_info())
138 self.set_extra_info_value(extra_info)
139 return super(Book, self).save(force_insert, force_update)
141 def get_mp3_info(self):
142 """Retrieves artist and director names from audio ID3 tags."""
143 audio = id3.ID3(self.mp3_file.path)
144 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
145 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
146 return {'artist_name': artist_name, 'director_name': director_name}
148 def has_description(self):
149 return len(self.description) > 0
150 has_description.short_description = _('description')
151 has_description.boolean = True
153 def has_pdf_file(self):
154 return bool(self.pdf_file)
155 has_pdf_file.short_description = 'PDF'
156 has_pdf_file.boolean = True
158 def has_odt_file(self):
159 return bool(self.odt_file)
160 has_odt_file.short_description = 'ODT'
161 has_odt_file.boolean = True
163 def has_html_file(self):
164 return bool(self.html_file)
165 has_html_file.short_description = 'HTML'
166 has_html_file.boolean = True
168 class AlreadyExists(Exception):
172 def from_xml_file(xml_file, overwrite=False):
173 from tempfile import NamedTemporaryFile
174 from slughifi import slughifi
175 from markupstring import MarkupString
178 book_info = dcparser.parse(xml_file)
179 book_base, book_slug = book_info.url.rsplit('/', 1)
180 book, created = Book.objects.get_or_create(slug=book_slug)
186 raise Book.AlreadyExists('Book %s already exists' % book_slug)
187 # Save shelves for this book
188 book_shelves = list(book.tags.filter(category='set'))
190 book.title = book_info.title
191 book.set_extra_info_value(book_info.to_dict())
192 book._short_html = ''
196 for category in ('kind', 'genre', 'author', 'epoch'):
197 tag_name = getattr(book_info, category)
198 tag_sort_key = tag_name
199 if category == 'author':
200 tag_sort_key = tag_name.last_name
201 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
202 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
205 tag.sort_key = slughifi(tag_sort_key)
206 tag.category = category
208 book_tags.append(tag)
210 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
212 book_tag.name = book.title[:50]
213 book_tag.sort_key = ('l-' + book.slug)[:120]
214 book_tag.category = 'book'
216 book_tags.append(book_tag)
218 book.tags = book_tags
220 if hasattr(book_info, 'parts'):
221 for n, part_url in enumerate(book_info.parts):
222 base, slug = part_url.rsplit('/', 1)
223 child_book = Book.objects.get(slug=slug)
224 child_book.parent = book
225 child_book.parent_number = n
228 book_descendants = list(book.children.all())
229 while len(book_descendants) > 0:
230 child_book = book_descendants.pop(0)
231 for fragment in child_book.fragments.all():
232 fragment.tags = set(list(fragment.tags) + [book_tag])
233 book_descendants += list(child_book.children.all())
235 # Save XML and HTML files
236 if not isinstance(xml_file, File):
237 xml_file = File(file(xml_file))
238 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
240 html_file = NamedTemporaryFile()
241 if html.transform(book.xml_file.path, html_file):
242 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
245 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
247 for fragment in closed_fragments.values():
248 text = fragment.to_string()
250 if (len(MarkupString(text)) > 240):
251 short_text = unicode(MarkupString(text)[:160])
252 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
253 defaults={'text': text, 'short_text': short_text})
256 theme_names = [s.strip() for s in fragment.themes.split(',')]
257 except AttributeError:
260 for theme_name in theme_names:
261 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
263 tag.name = theme_name
264 tag.sort_key = slughifi(theme_name)
265 tag.category = 'theme'
269 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
270 book_themes += themes
272 book_themes = set(book_themes)
273 book.tags = list(book.tags) + list(book_themes) + book_shelves
279 def get_absolute_url(self):
280 return ('catalogue.views.book_detail', [self.slug])
283 ordering = ('title',)
284 verbose_name = _('book')
285 verbose_name_plural = _('books')
287 def __unicode__(self):
291 class Fragment(models.Model):
292 text = models.TextField()
293 short_text = models.TextField(editable=False)
294 _short_html = models.TextField(editable=False)
295 anchor = models.CharField(max_length=120)
296 book = models.ForeignKey(Book, related_name='fragments')
298 objects = models.Manager()
299 tagged = managers.ModelTaggedItemManager(Tag)
300 tags = managers.TagDescriptor(Tag)
302 def short_html(self):
303 if len(self._short_html):
304 return mark_safe(self._short_html)
306 book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)
307 for tag in self.book.tags if tag.category == 'author']
309 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
310 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
312 return mark_safe(self._short_html)
314 def get_absolute_url(self):
315 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
318 ordering = ('book', 'anchor',)
319 verbose_name = _('fragment')
320 verbose_name_plural = _('fragments')