1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 from django.contrib.contenttypes.models import ContentType
6 from django.db.models import Count
7 from .models import Tag, Book
10 BOOK_CATEGORIES = ('author', 'epoch', 'genre', 'kind')
13 def get_top_level_related_tags(tags=None, categories=BOOK_CATEGORIES):
15 Finds tags related to given tags through books, and counts their usage.
17 Takes ancestry into account: if a tag is applied to a book, its
18 usage on the book's descendants is ignored.
20 This is tested for PostgreSQL 9.1+, and might not work elsewhere.
21 It particular, it uses raw SQL using WITH clause, which is
22 supported in SQLite from v. 3.8.3, and is missing in MySQL.
23 http://bugs.mysql.com/bug.php?id=16244
26 # First, find all tag relations of relevant books.
27 bct = ContentType.objects.get_for_model(Book)
28 relations = Tag.intermediary_table_model.objects.filter(
31 tagged_books = Book.tagged.with_all(tags).only('pk')
32 relations = relations.filter(
33 object_id__in=tagged_books).exclude(
34 tag_id__in=[tag.pk for tag in tags])
36 rel_sql, rel_params = relations.query.sql_with_params()
38 # Exclude those relations between a book and a tag,
39 # for which there is a relation between the book's ancestor
42 return Tag.objects.raw('''
43 WITH AllTagged AS (''' + rel_sql + ''')
44 SELECT catalogue_tag.*, COUNT(catalogue_tag.id) AS count
45 FROM catalogue_tag, AllTagged
46 WHERE catalogue_tag.id=AllTagged.tag_id
47 AND catalogue_tag.category IN %s
49 SELECT AncestorTagged.id
50 FROM catalogue_book_ancestor Ancestor,
51 AllTagged AncestorTagged
52 WHERE Ancestor.from_book_id=AllTagged.object_id
53 AND AncestorTagged.content_type_id=%s
54 AND AncestorTagged.object_id=Ancestor.to_book_id
55 AND AncestorTagged.tag_id=AllTagged.tag_id
57 GROUP BY catalogue_tag.id
58 ORDER BY sort_key''', rel_params + (categories, bct.pk))