+ book.xml_file.save('%s.xml' % book.slug, raw_file, save=False)
+
+ # delete old fragments when overwriting
+ book.fragments.all().delete()
+
+ if book.build_html():
+ if not settings.NO_BUILD_TXT and build_txt:
+ book.build_txt()
+
+ book.build_cover(book_info)
+
+ if not settings.NO_BUILD_EPUB and build_epub:
+ book.build_epub()
+
+ if not settings.NO_BUILD_PDF and build_pdf:
+ book.build_pdf()
+
+ if not settings.NO_BUILD_MOBI and build_mobi:
+ book.build_mobi()
+
+ if not settings.NO_SEARCH_INDEX and search_index:
+ book.search_index()
+ #index_book.delay(book.id, book_info)
+
+ book_descendants = list(book.children.all())
+ descendants_tags = set()
+ # add l-tag to descendants and their fragments
+ while len(book_descendants) > 0:
+ child_book = book_descendants.pop(0)
+ descendants_tags.update(child_book.tags)
+ child_book.tags = list(child_book.tags) + [book_tag]
+ child_book.save()
+ for fragment in child_book.fragments.all():
+ fragment.tags = set(list(fragment.tags) + [book_tag])
+ book_descendants += list(child_book.children.all())
+
+ for tag in descendants_tags:
+ touch_tag.delay(tag)
+
+ book.save()
+
+ # refresh cache
+ book.reset_tag_counter()
+ book.reset_theme_counter()
+
+ 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.get_absolute_url()) for t in tags[category]]
+ for media_format in BookMedia.formats:
+ rel['media'][media_format] = self.has_media(media_format)
+ 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
+ permanent_cache.delete(cache_key)
+ if self.parent:
+ self.parent.reset_tag_counter()
+
+ @property
+ def tag_counter(self):
+ if self.id:
+ cache_key = "Book.tag_counter/%d" % self.id
+ tags = permanent_cache.get(cache_key)
+ else:
+ tags = None
+
+ if tags is None:
+ tags = {}
+ for child in self.children.all().order_by():
+ for tag_pk, value in child.tag_counter.iteritems():
+ tags[tag_pk] = tags.get(tag_pk, 0) + value
+ for tag in self.tags.exclude(category__in=('book', 'theme', 'set')).order_by():
+ tags[tag.pk] = 1
+
+ if self.id:
+ permanent_cache.set(cache_key, tags)
+ return tags
+
+ def reset_theme_counter(self):
+ if self.id is None:
+ return
+
+ cache_key = "Book.theme_counter/%d" % self.id
+ permanent_cache.delete(cache_key)
+ if self.parent:
+ self.parent.reset_theme_counter()
+
+ @property
+ def theme_counter(self):
+ if self.id:
+ cache_key = "Book.theme_counter/%d" % self.id
+ tags = permanent_cache.get(cache_key)
+ else:
+ tags = None
+
+ if tags is None:
+ tags = {}
+ for fragment in Fragment.tagged.with_any([self.book_tag()]).order_by():
+ for tag in fragment.tags.filter(category='theme').order_by():
+ tags[tag.pk] = tags.get(tag.pk, 0) + 1
+
+ if self.id:
+ permanent_cache.set(cache_key, tags)
+ return tags
+
+ def pretty_title(self, html_links=False):
+ book = self
+ names = list(book.tags.filter(category='author'))
+
+ books = []
+ while book:
+ books.append(book)
+ book = book.parent
+ names.extend(reversed(books))
+
+ if html_links:
+ names = ['<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in names]
+ else:
+ names = [tag.name for tag in names]
+
+ return ', '.join(names)
+
+ @classmethod
+ def tagged_top_level(cls, tags):
+ """ Returns top-level books tagged with `tags'.
+
+ It only returns those books which don't have ancestors which are
+ also tagged with those tags.
+
+ """
+ # get relevant books and their tags
+ objects = cls.tagged.with_all(tags)
+ # eliminate descendants
+ l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in objects])
+ descendants_keys = [book.pk for book in cls.tagged.with_any(l_tags)]
+ if descendants_keys:
+ objects = objects.exclude(pk__in=descendants_keys)
+
+ return objects
+
+ @classmethod
+ def book_list(cls, filter=None):
+ """Generates a hierarchical listing of all books.
+
+ Books are optionally filtered with a test function.
+
+ """
+
+ books_by_parent = {}
+ books = cls.objects.all().order_by('parent_number', 'sort_key').only(
+ 'title', 'parent', 'slug')
+ if filter:
+ books = books.filter(filter).distinct()
+ book_ids = set((book.pk for book in books))
+ for book in books:
+ parent = book.parent_id
+ if parent not in book_ids:
+ parent = None
+ books_by_parent.setdefault(parent, []).append(book)
+ else:
+ for book in books:
+ books_by_parent.setdefault(book.parent_id, []).append(book)
+
+ orphans = []
+ books_by_author = SortedDict()
+ for tag in Tag.objects.filter(category='author'):
+ books_by_author[tag] = []
+
+ for book in books_by_parent.get(None,()):
+ authors = list(book.tags.filter(category='author'))
+ if authors:
+ for author in authors:
+ books_by_author[author].append(book)
+ else:
+ orphans.append(book)
+
+ return books_by_author, orphans, books_by_parent
+
+ _audiences_pl = {
+ "SP1": (1, u"szkoła podstawowa"),
+ "SP2": (1, u"szkoła podstawowa"),
+ "P": (1, u"szkoła podstawowa"),
+ "G": (2, u"gimnazjum"),
+ "L": (3, u"liceum"),
+ "LP": (3, u"liceum"),
+ }
+ def audiences_pl(self):
+ audiences = self.get_extra_info_value().get('audiences', [])
+ 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))
+ has.short_description = t.upper()
+ has.boolean = True
+ has.__name__ = "has_%s_file" % ftype
+ return has
+