from django.db import models
from django.db.models import permalink, Q
import django.dispatch
-from django.core.cache import cache
+from django.core.cache import get_cache
from django.core.files.storage import DefaultStorage
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
from django.utils.translation import get_language
from django.core.urlresolvers import reverse
-from django.db.models.signals import post_save, m2m_changed, pre_delete
+from django.db.models.signals import post_save, m2m_changed, pre_delete, post_delete
+import jsonfield
from django.conf import settings
from newtagging.models import TagBase, tags_updated
from newtagging import managers
from catalogue.fields import JSONField, OverwritingFileField
-from catalogue.utils import create_zip, split_tags
+from catalogue.utils import create_zip, split_tags, truncate_html_words
from catalogue.tasks import touch_tag, index_book
from shutil import copy
from glob import glob
import re
from os import path
-
import search
# Those are hard-coded here so that makemessages sees them.
('book', _('book')),
)
-# not quite, but Django wants you to set a timeout
-CACHE_FOREVER = 2419200 # 28 days
+
+permanent_cache = get_cache('permanent')
class TagSubcategoryManager(models.Manager):
return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen)
+def customizations_hash(customizations):
+ customizations.sort()
+ return hash(tuple(customizations))
+
+
def get_customized_pdf_path(book, customizations):
"""
Returns a MEDIA_ROOT relative path for a customized pdf. The name will contain a hash of customization options.
"""
- customizations.sort()
- h = hash(tuple(customizations))
-
+ h = customizations_hash(customizations)
pdf_name = '%s-custom-%s' % (book.slug, h)
pdf_file = get_dynamic_path(None, pdf_name, ext='pdf')
formats = ebook_formats + ['html', 'xml']
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
+
+ _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)
+
objects = models.Manager()
tagged = managers.ModelTaggedItemManager(Tag)
tags = managers.TagDescriptor(Tag)
if self.id is None:
return
- cache_key = "Book.short_html/%d/%s"
- for lang, langname in settings.LANGUAGES:
- cache.delete(cache_key % (self.id, lang))
- cache.delete("Book.mini_box/%d" % (self.id, ))
+ type(self).objects.filter(pk=self.pk).update(_related_info=None)
# Fragment.short_html relies on book's tags, so reset it here too
for fragm in self.fragments.all():
fragm.reset_short_html()
- def short_html(self):
- if self.id:
- cache_key = "Book.short_html/%d/%s" % (self.id, get_language())
- short_html = cache.get(cache_key)
- else:
- short_html = None
-
- if short_html is not None:
- return mark_safe(short_html)
- else:
- tags = self.tags.filter(category__in=('author', 'kind', 'genre', 'epoch'))
- tags = split_tags(tags)
-
- formats = {}
- # files generated during publication
- for ebook_format in self.ebook_formats:
- if self.has_media(ebook_format):
- formats[ebook_format] = self.get_media(ebook_format)
-
-
- short_html = unicode(render_to_string('catalogue/book_short.html',
- {'book': self, 'tags': tags, 'formats': formats}))
-
- if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
- return mark_safe(short_html)
-
- def mini_box(self):
- if self.id:
- cache_key = "Book.mini_box/%d" % (self.id, )
- short_html = cache.get(cache_key)
- else:
- short_html = None
-
- if short_html is None:
- authors = self.tags.filter(category='author')
-
- short_html = unicode(render_to_string('catalogue/book_mini_box.html',
- {'book': self, 'authors': authors, 'STATIC_URL': settings.STATIC_URL}))
-
- if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
- return mark_safe(short_html)
-
def has_description(self):
return len(self.description) > 0
has_description.short_description = _('description')
def build_html(self):
- from markupstring import MarkupString
from django.core.files.base import ContentFile
from slughifi import slughifi
from librarian import html
continue
text = fragment.to_string()
- short_text = ''
- if (len(MarkupString(text)) > 240):
- short_text = unicode(MarkupString(text)[:160])
+ short_text = truncate_html_words(text, 15)
+ if text == short_text:
+ short_text = ''
new_fragment = Fragment.objects.create(anchor=fragment.id, book=self,
text=text, short_text=short_text)
result = create_zip.delay(paths, "%s_%s" % (self.slug, format_))
return result.wait()
- def search_index(self, book_info=None, reuse_index=False):
+ def search_index(self, book_info=None, reuse_index=False, index_tags=True):
if reuse_index:
idx = search.ReusableIndex()
else:
idx.open()
try:
idx.index_book(self, book_info)
- idx.index_tags()
+ if index_tags:
+ idx.index_tags()
finally:
idx.close()
@classmethod
def from_text_and_meta(cls, raw_file, book_info, overwrite=False,
build_epub=True, build_txt=True, build_pdf=True, build_mobi=True,
- search_index=True):
+ search_index=True, search_index_tags=True, search_index_reuse=False):
import re
from sortify import sortify
book.build_mobi()
if not settings.NO_SEARCH_INDEX and search_index:
- index_book.delay(book.id, book_info)
+ book.search_index(index_tags=search_index_tags, reuse_index=search_index_reuse)
+ #index_book.delay(book.id, book_info)
book_descendants = list(book.children.all())
descendants_tags = set()
book_descendants += list(child_book.children.all())
for tag in descendants_tags:
- touch_tag.delay(tag)
+ touch_tag(tag)
book.save()
cls.published.send(sender=book)
return book
+ def related_info(self):
+ """Keeps info about related objects (tags, media) in cache field."""
+ if self._related_info is not None:
+ return self._related_info
+ else:
+ rel = {'tags': {}, 'media': {}}
+
+ tags = self.tags.filter(category__in=(
+ 'author', 'kind', 'genre', 'epoch'))
+ tags = split_tags(tags)
+ for category in tags:
+ rel['tags'][category] = [
+ (t.name, t.slug) for t in tags[category]]
+
+ for media_format in BookMedia.formats:
+ rel['media'][media_format] = self.has_media(media_format)
+
+ book = self
+ parents = []
+ while book.parent:
+ parents.append((book.parent.title, book.parent.slug))
+ book = book.parent
+ parents = parents[::-1]
+ if parents:
+ rel['parents'] = parents
+
+ if self.pk:
+ type(self).objects.filter(pk=self.pk).update(_related_info=rel)
+ return rel
+
def reset_tag_counter(self):
if self.id is None:
return
cache_key = "Book.tag_counter/%d" % self.id
- cache.delete(cache_key)
+ permanent_cache.delete(cache_key)
if self.parent:
self.parent.reset_tag_counter()
def tag_counter(self):
if self.id:
cache_key = "Book.tag_counter/%d" % self.id
- tags = cache.get(cache_key)
+ tags = permanent_cache.get(cache_key)
else:
tags = None
tags[tag.pk] = 1
if self.id:
- cache.set(cache_key, tags, CACHE_FOREVER)
+ permanent_cache.set(cache_key, tags)
return tags
def reset_theme_counter(self):
return
cache_key = "Book.theme_counter/%d" % self.id
- cache.delete(cache_key)
+ permanent_cache.delete(cache_key)
if self.parent:
self.parent.reset_theme_counter()
def theme_counter(self):
if self.id:
cache_key = "Book.theme_counter/%d" % self.id
- tags = cache.get(cache_key)
+ tags = permanent_cache.get(cache_key)
else:
tags = None
tags[tag.pk] = tags.get(tag.pk, 0) + 1
if self.id:
- cache.set(cache_key, tags, CACHE_FOREVER)
+ permanent_cache.set(cache_key, tags)
return tags
def pretty_title(self, html_links=False):
audiences = sorted(set([self._audiences_pl[a] for a in audiences]))
return [a[1] for a in audiences]
+ def choose_fragment(self):
+ tag = self.book_tag()
+ fragments = Fragment.tagged.with_any([tag])
+ if fragments.exists():
+ return fragments.order_by('?')[0]
+ elif self.parent:
+ return self.parent.choose_fragment()
+ else:
+ return None
+
def _has_factory(ftype):
has = lambda self: bool(getattr(self, "%s_file" % ftype))
cache_key = "Fragment.short_html/%d/%s"
for lang, langname in settings.LANGUAGES:
- cache.delete(cache_key % (self.id, lang))
+ permanent_cache.delete(cache_key % (self.id, lang))
+
+ def get_short_text(self):
+ """Returns short version of the fragment."""
+ return self.short_text if self.short_text else self.text
def short_html(self):
if self.id:
cache_key = "Fragment.short_html/%d/%s" % (self.id, get_language())
- short_html = cache.get(cache_key)
+ short_html = permanent_cache.get(cache_key)
else:
short_html = None
short_html = unicode(render_to_string('catalogue/fragment_short.html',
{'fragment': self}))
if self.id:
- cache.set(cache_key, short_html, CACHE_FOREVER)
+ permanent_cache.set(cache_key, short_html)
return mark_safe(short_html)
# reset tag global counter
# we want Tag.changed_at updated for API to know the tag was touched
for tag in affected_tags:
- touch_tag.delay(tag)
+ touch_tag(tag)
# if book tags changed, reset book tag counter
if isinstance(sender, Book) and \
instance.book.save()
pre_delete.connect(_pre_delete_handler)
+
def _post_save_handler(sender, instance, **kwargs):
""" refresh all the short_html stuff on BookMedia update """
if sender == BookMedia:
instance.book.save()
post_save.connect(_post_save_handler)
+
+
+@django.dispatch.receiver(post_delete, sender=Book)
+def _remove_book_from_index_handler(sender, instance, **kwargs):
+ """ remove the book from search index, when it is deleted."""
+ idx = search.Index()
+ idx.open(timeout=10000) # 10 seconds timeout.
+ try:
+ idx.remove_book(instance)
+ finally:
+ idx.close()