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 = [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 self._short_html = unicode(render_to_string('catalogue/book_short.html',
139 {'book': self, 'tags': tags, 'formats': formats}))
141 return mark_safe(self._short_html)
143 def save(self, force_insert=False, force_update=False):
145 extra_info = self.get_extra_info_value()
146 extra_info.update(self.get_mp3_info())
147 self.set_extra_info_value(extra_info)
148 return super(Book, self).save(force_insert, force_update)
150 def get_mp3_info(self):
151 """Retrieves artist and director names from audio ID3 tags."""
152 audio = id3.ID3(self.mp3_file.path)
153 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
154 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
155 return {'artist_name': artist_name, 'director_name': director_name}
157 def has_description(self):
158 return len(self.description) > 0
159 has_description.short_description = _('description')
160 has_description.boolean = True
162 def has_pdf_file(self):
163 return bool(self.pdf_file)
164 has_pdf_file.short_description = 'PDF'
165 has_pdf_file.boolean = True
167 def has_odt_file(self):
168 return bool(self.odt_file)
169 has_odt_file.short_description = 'ODT'
170 has_odt_file.boolean = True
172 def has_html_file(self):
173 return bool(self.html_file)
174 has_html_file.short_description = 'HTML'
175 has_html_file.boolean = True
177 class AlreadyExists(Exception):
181 def from_xml_file(xml_file, overwrite=False):
182 from tempfile import NamedTemporaryFile
183 from slughifi import slughifi
184 from markupstring import MarkupString
187 book_info = dcparser.parse(xml_file)
188 book_base, book_slug = book_info.url.rsplit('/', 1)
189 book, created = Book.objects.get_or_create(slug=book_slug)
195 raise Book.AlreadyExists('Book %s already exists' % book_slug)
196 # Save shelves for this book
197 book_shelves = list(book.tags.filter(category='set'))
199 book.title = book_info.title
200 book.set_extra_info_value(book_info.to_dict())
201 book._short_html = ''
205 for category in ('kind', 'genre', 'author', 'epoch'):
206 tag_name = getattr(book_info, category)
207 tag_sort_key = tag_name
208 if category == 'author':
209 tag_sort_key = tag_name.last_name
210 tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
211 tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name))
214 tag.sort_key = slughifi(tag_sort_key)
215 tag.category = category
217 book_tags.append(tag)
219 book_tag, created = Tag.objects.get_or_create(slug=('l-' + book.slug)[:120])
221 book_tag.name = book.title[:50]
222 book_tag.sort_key = ('l-' + book.slug)[:120]
223 book_tag.category = 'book'
225 book_tags.append(book_tag)
227 book.tags = book_tags
229 if hasattr(book_info, 'parts'):
230 for n, part_url in enumerate(book_info.parts):
231 base, slug = part_url.rsplit('/', 1)
232 child_book = Book.objects.get(slug=slug)
233 child_book.parent = book
234 child_book.parent_number = n
237 book_descendants = list(book.children.all())
238 while len(book_descendants) > 0:
239 child_book = book_descendants.pop(0)
240 for fragment in child_book.fragments.all():
241 fragment.tags = set(list(fragment.tags) + [book_tag])
242 book_descendants += list(child_book.children.all())
244 # Save XML and HTML files
245 if not isinstance(xml_file, File):
246 xml_file = File(file(xml_file))
247 book.xml_file.save('%s.xml' % book.slug, xml_file, save=False)
249 html_file = NamedTemporaryFile()
250 if html.transform(book.xml_file.path, html_file):
251 book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
254 closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
256 for fragment in closed_fragments.values():
257 text = fragment.to_string()
259 if (len(MarkupString(text)) > 240):
260 short_text = unicode(MarkupString(text)[:160])
261 new_fragment, created = Fragment.objects.get_or_create(anchor=fragment.id, book=book,
262 defaults={'text': text, 'short_text': short_text})
265 theme_names = [s.strip() for s in fragment.themes.split(',')]
266 except AttributeError:
269 for theme_name in theme_names:
270 tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name))
272 tag.name = theme_name
273 tag.sort_key = slughifi(theme_name)
274 tag.category = 'theme'
278 new_fragment.tags = set(list(book.tags) + themes + [book_tag])
279 book_themes += themes
281 book_themes = set(book_themes)
282 book.tags = list(book.tags) + list(book_themes) + book_shelves
288 def get_absolute_url(self):
289 return ('catalogue.views.book_detail', [self.slug])
292 ordering = ('title',)
293 verbose_name = _('book')
294 verbose_name_plural = _('books')
296 def __unicode__(self):
300 class Fragment(models.Model):
301 text = models.TextField()
302 short_text = models.TextField(editable=False)
303 _short_html = models.TextField(editable=False)
304 anchor = models.CharField(max_length=120)
305 book = models.ForeignKey(Book, related_name='fragments')
307 objects = models.Manager()
308 tagged = managers.ModelTaggedItemManager(Tag)
309 tags = managers.TagDescriptor(Tag)
311 def short_html(self):
312 if len(self._short_html):
313 return mark_safe(self._short_html)
315 book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name)
316 for tag in self.book.tags if tag.category == 'author']
318 self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
319 {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
321 return mark_safe(self._short_html)
323 def get_absolute_url(self):
324 return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor)
327 ordering = ('book', 'anchor',)
328 verbose_name = _('fragment')
329 verbose_name_plural = _('fragments')