+ if self.id:
+ cache_key = "Book.theme_counter/%d" % self.id
+ tags = 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:
+ cache.set(cache_key, tags, CACHE_FOREVER)
+ 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
+
+
+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
+
+
+# add the file fields
+for t in Book.file_types:
+ field_name = "%s_file" % t
+ models.FileField(_("%s file" % t.upper()),
+ upload_to=book_upload_path(t),
+ blank=True).contribute_to_class(Book, field_name)
+
+ setattr(Book, "has_%s_file" % t, _has_factory(t))
+