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">PDF</a>' % self.pdf_file.url)
125 formats.append(u'<a href="%s">ODT</a>' % self.odt_file.url)
127 formats.append(u'<a href="%s">TXT</a>' % self.txt_file.url)
129 formats.append(u'<a href="%s">MP3</a>' % self.mp3_file.url)
131 formats.append(u'<a href="%s">OGG</a>' % self.ogg_file.url)
133 self._short_html = unicode(render_to_string('catalogue/book_short.html',
134 {'book': self, 'tags': tags, 'formats': formats}))
136 return mark_safe(self._short_html)
138 def save(self, force_insert=False, force_update=False):
140 extra_info = self.get_extra_info_value()
141 extra_info.update(self.get_mp3_info())
142 self.set_extra_info_value(extra_info)
143 return super(Book, self).save(force_insert, force_update)
145 def get_mp3_info(self):
146 """Retrieves artist and director names from audio ID3 tags."""
147 audio = id3.ID3(self.mp3_file.path)
148 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
149 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
150 return {'artist_name': artist_name, 'director_name': director_name}
152 def has_description(self):
153 return len(self.description) > 0
154 has_description.short_description = _('description')
155 has_description.boolean = True
157 def has_pdf_file(self):
158 return bool(self.pdf_file)
159 has_pdf_file.short_description = 'PDF'
160 has_pdf_file.boolean = True
162 def has_odt_file(self):
163 return bool(self.odt_file)
164 has_odt_file.short_description = 'ODT'
165 has_odt_file.boolean = True
167 def has_html_file(self):
168 return bool(self.html_file)
169 has_html_file.short_description = 'HTML'
170 has_html_file.boolean = True
172 class AlreadyExists(Exception):
176 def from_xml_file(xml_file, overwrite=False):
177 from tempfile import NamedTemporaryFile
178 from slughifi import slughifi
179 from markupstring import MarkupString
182 book_info = dcparser.parse(xml_file)
183 book_base, book_slug = book_info.url.rsplit('/', 1)
184 book, created = Book.objects.get_or_create(slug=book_slug)
190 raise Book.AlreadyExists('Book %s already exists' % book_slug)
191 # Save shelves for this book
192 book_shelves = list(book.tags.filter(category='set'))
194 book.title = book_info.title
195 book.set_extra_info_value(book_info.to_dict())
196 book._short_html = ''
200 for category in ('kind', 'genre', 'author', 'epoch'):
201 tag_name = getattr(book_info, category)
202 tag_sort_key = tag_name
203 if category == 'author':
204 tag_sort_key = tag_name.last_name
205 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
206 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
209 tag.sort_key = slughifi(tag_sort_key)
210 tag.category = category
212 book_tags.append(tag)
214 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
216 book_tag.name = book.title[:50]
217 book_tag.sort_key = ('l-' + book.slug)[:120]
218 book_tag.category = 'book'
220 book_tags.append(book_tag)
222 book.tags = book_tags
224 if hasattr(book_info, 'parts'):
225 for n, part_url in enumerate(book_info.parts):
226 base, slug = part_url.rsplit('/', 1)
227 child_book = Book.objects.get(slug=slug)
228 child_book.parent = book
229 child_book.parent_number = n
232 book_descendants = list(book.children.all())
233 while len(book_descendants) > 0:
234 child_book = book_descendants.pop(0)
235 for fragment in child_book.fragments.all():
236 fragment.tags = set(list(fragment.tags) + [book_tag])
237 book_descendants += list(child_book.children.all())
239 # Save XML and HTML files
240 if not isinstance(xml_file, File):
241 xml_file = File(file(xml_file))
242 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
244 html_file = NamedTemporaryFile()
245 if html.transform(book.xml_file.path, html_file):
246 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
249 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
251 for fragment in closed_fragments.values():
252 text = fragment.to_string()
254 if (len(MarkupString(text)) > 240):
255 short_text = unicode(MarkupString(text)[:160])
256 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
257 defaults={'text': text, 'short_text': short_text})
260 theme_names = [s.strip() for s in fragment.themes.split(',')]
261 except AttributeError:
264 for theme_name in theme_names:
265 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
267 tag.name = theme_name
268 tag.sort_key = slughifi(theme_name)
269 tag.category = 'theme'
273 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
274 book_themes += themes
276 book_themes = set(book_themes)
277 book.tags = list(book.tags) + list(book_themes) + book_shelves
283 def get_absolute_url(self):
284 return ('catalogue.views.book_detail', [self.slug])
287 ordering = ('title',)
288 verbose_name = _('book')
289 verbose_name_plural = _('books')
291 def __unicode__(self):
295 class Fragment(models.Model):
296 text = models.TextField()
297 short_text = models.TextField(editable=False)
298 _short_html = models.TextField(editable=False)
299 anchor = models.CharField(max_length=120)
300 book = models.ForeignKey(Book, related_name='fragments')
302 objects = models.Manager()
303 tagged = managers.ModelTaggedItemManager(Tag)
304 tags = managers.TagDescriptor(Tag)
306 def short_html(self):
307 if len(self._short_html):
308 return mark_safe(self._short_html)
310 book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)
311 for tag in self.book.tags if tag.category == 'author']
313 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
314 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
316 return mark_safe(self._short_html)
318 def get_absolute_url(self):
319 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
322 ordering = ('book', 'anchor',)
323 verbose_name = _('fragment')
324 verbose_name_plural = _('fragments')