Preliminary math and tables support.
[wolnelektury.git] / apps / catalogue / helpers.py
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.
4 #
5 from django.contrib.contenttypes.models import ContentType
6 from django.db.models import Count
7 from .models import Tag, Book
8
9
10 BOOK_CATEGORIES = ('author', 'epoch', 'genre', 'kind')
11
12
13 def get_top_level_related_tags(tags=None, categories=BOOK_CATEGORIES):
14     """
15     Finds tags related to given tags through books, and counts their usage.
16
17     Takes ancestry into account: if a tag is applied to a book, its
18     usage on the book's descendants is ignored.
19
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
24
25     """
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(
29         content_type=bct)
30     if tags is not None:
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])
35
36     rel_sql, rel_params = relations.query.sql_with_params()
37
38     # Exclude those relations between a book and a tag,
39     # for which there is a relation between the book's ancestor
40     # and the tag and 
41
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
48             AND NOT EXISTS (
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
56             )
57         GROUP BY catalogue_tag.id
58         ORDER BY sort_key''', rel_params + (categories, bct.pk))