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)
52 def has_description(self):
53 return len(self.description) > 0
54 has_description.short_description = _('description')
55 has_description.boolean = True
58 def get_absolute_url(self):
59 return ('catalogue.views.tagged_object_list', [self.slug])
62 ordering = ('sort_key',)
63 verbose_name = _('tag')
64 verbose_name_plural = _('tags')
66 def __unicode__(self):
70 def get_tag_list(tags):
71 if isinstance(tags, basestring):
72 tag_slugs = tags.split('/')
73 return [Tag.objects.get(slug=slug) for slug in tag_slugs]
75 return TagBase.get_tag_list(tags)
78 def book_upload_path(ext):
79 def get_dynamic_path(book, filename):
80 return 'lektura/%s.%s' % (book.slug, ext)
81 return get_dynamic_path
84 class Book(models.Model):
85 title = models.CharField(_('title'), max_length=120)
86 slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True)
87 description = models.TextField(_('description'), blank=True)
88 created_at = models.DateTimeField(_('creation date'), auto_now=True)
89 _short_html = models.TextField(_('short HTML'), editable=False)
90 parent_number = models.IntegerField(_('parent number'), default=0)
91 extra_info = JSONField(_('extra information'))
92 gazeta_link = models.CharField(blank=True, max_length=240)
95 xml_file = models.FileField(_('XML file'), upload_to=book_upload_path('xml'), blank=True)
96 html_file = models.FileField(_('HTML file'), upload_to=book_upload_path('html'), blank=True)
97 pdf_file = models.FileField(_('PDF file'), upload_to=book_upload_path('pdf'), blank=True)
98 odt_file = models.FileField(_('ODT file'), upload_to=book_upload_path('odt'), blank=True)
99 txt_file = models.FileField(_('TXT file'), upload_to=book_upload_path('txt'), blank=True)
100 mp3_file = models.FileField(_('MP3 file'), upload_to=book_upload_path('mp3'), blank=True)
101 ogg_file = models.FileField(_('OGG file'), upload_to=book_upload_path('ogg'), blank=True)
103 parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
105 objects = models.Manager()
106 tagged = managers.ModelTaggedItemManager(Tag)
107 tags = managers.TagDescriptor(Tag)
114 def short_html(self):
115 if len(self._short_html):
116 return mark_safe(self._short_html)
118 tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book')))
119 tags = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in tags]
123 formats.append(u'<a href="%s">Czytaj online</a>' % reverse('book_text', kwargs={'slug': self.slug}))
125 formats.append(u'<a href="%s">PDF</a>' % self.pdf_file.url)
127 formats.append(u'<a href="%s">ODT</a>' % self.odt_file.url)
129 formats.append(u'<a href="%s">TXT</a>' % self.txt_file.url)
131 formats.append(u'<a href="%s">MP3</a>' % self.mp3_file.url)
133 formats.append(u'<a href="%s">OGG</a>' % self.ogg_file.url)
135 self._short_html = unicode(render_to_string('catalogue/book_short.html',
136 {'book': self, 'tags': tags, 'formats': formats}))
138 return mark_safe(self._short_html)
140 def save(self, force_insert=False, force_update=False):
142 extra_info = self.get_extra_info_value()
143 extra_info.update(self.get_mp3_info())
144 self.set_extra_info_value(extra_info)
145 return super(Book, self).save(force_insert, force_update)
147 def get_mp3_info(self):
148 """Retrieves artist and director names from audio ID3 tags."""
149 audio = id3.ID3(self.mp3_file.path)
150 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
151 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
152 return {'artist_name': artist_name, 'director_name': director_name}
154 def has_description(self):
155 return len(self.description) > 0
156 has_description.short_description = _('description')
157 has_description.boolean = True
159 def has_pdf_file(self):
160 return bool(self.pdf_file)
161 has_pdf_file.short_description = 'PDF'
162 has_pdf_file.boolean = True
164 def has_odt_file(self):
165 return bool(self.odt_file)
166 has_odt_file.short_description = 'ODT'
167 has_odt_file.boolean = True
169 def has_html_file(self):
170 return bool(self.html_file)
171 has_html_file.short_description = 'HTML'
172 has_html_file.boolean = True
174 class AlreadyExists(Exception):
178 def from_xml_file(xml_file, overwrite=False):
179 from tempfile import NamedTemporaryFile
180 from slughifi import slughifi
181 from markupstring import MarkupString
184 book_info = dcparser.parse(xml_file)
185 book_base, book_slug = book_info.url.rsplit('/', 1)
186 book, created = Book.objects.get_or_create(slug=book_slug)
192 raise Book.AlreadyExists('Book %s already exists' % book_slug)
193 # Save shelves for this book
194 book_shelves = list(book.tags.filter(category='set'))
196 book.title = book_info.title
197 book.set_extra_info_value(book_info.to_dict())
198 book._short_html = ''
202 for category in ('kind', 'genre', 'author', 'epoch'):
203 tag_name = getattr(book_info, category)
204 tag_sort_key = tag_name
205 if category == 'author':
206 tag_sort_key = tag_name.last_name
207 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
208 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
211 tag.sort_key = slughifi(tag_sort_key)
212 tag.category = category
214 book_tags.append(tag)
216 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
218 book_tag.name = book.title[:50]
219 book_tag.sort_key = ('l-' + book.slug)[:120]
220 book_tag.category = 'book'
222 book_tags.append(book_tag)
224 book.tags = book_tags
226 if hasattr(book_info, 'parts'):
227 for n, part_url in enumerate(book_info.parts):
228 base, slug = part_url.rsplit('/', 1)
229 child_book = Book.objects.get(slug=slug)
230 child_book.parent = book
231 child_book.parent_number = n
234 book_descendants = list(book.children.all())
235 while len(book_descendants) > 0:
236 child_book = book_descendants.pop(0)
237 for fragment in child_book.fragments.all():
238 fragment.tags = set(list(fragment.tags) + [book_tag])
239 book_descendants += list(child_book.children.all())
241 # Save XML and HTML files
242 if not isinstance(xml_file, File):
243 xml_file = File(file(xml_file))
244 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
246 html_file = NamedTemporaryFile()
247 if html.transform(book.xml_file.path, html_file):
248 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
251 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
253 for fragment in closed_fragments.values():
254 text = fragment.to_string()
256 if (len(MarkupString(text)) > 240):
257 short_text = unicode(MarkupString(text)[:160])
258 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
259 defaults={'text': text, 'short_text': short_text})
262 theme_names = [s.strip() for s in fragment.themes.split(',')]
263 except AttributeError:
266 for theme_name in theme_names:
267 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
269 tag.name = theme_name
270 tag.sort_key = slughifi(theme_name)
271 tag.category = 'theme'
275 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
276 book_themes += themes
278 book_themes = set(book_themes)
279 book.tags = list(book.tags) + list(book_themes) + book_shelves
285 def get_absolute_url(self):
286 return ('catalogue.views.book_detail', [self.slug])
289 ordering = ('title',)
290 verbose_name = _('book')
291 verbose_name_plural = _('books')
293 def __unicode__(self):
297 class Fragment(models.Model):
298 text = models.TextField()
299 short_text = models.TextField(editable=False)
300 _short_html = models.TextField(editable=False)
301 anchor = models.CharField(max_length=120)
302 book = models.ForeignKey(Book, related_name='fragments')
304 objects = models.Manager()
305 tagged = managers.ModelTaggedItemManager(Tag)
306 tags = managers.TagDescriptor(Tag)
308 def short_html(self):
309 if len(self._short_html):
310 return mark_safe(self._short_html)
312 book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)
313 for tag in self.book.tags if tag.category == 'author']
315 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
316 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
318 return mark_safe(self._short_html)
320 def get_absolute_url(self):
321 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
324 ordering = ('book', 'anchor',)
325 verbose_name = _('fragment')
326 verbose_name_plural = _('fragments')