1 from django.db import connection
2 from django.contrib.contenttypes.models import ContentType
3 from django.utils.translation import get_language
4 from picture.models import Picture, PictureArea
5 from catalogue.models import Fragment, Tag, Book
8 def _get_tag_relations_sql(tags):
10 SELECT Rx.object_id, Rx.content_type_id
11 FROM catalogue_tag_relation Rx"""
13 where = ['WHERE Rx.tag_id = %d' % tags[0].pk]
14 for i, tag in enumerate(tags[1:]):
15 joins.append('INNER JOIN catalogue_tag_relation TR%(i)d '
16 'ON TR%(i)d.object_id = Rx.object_id '
17 'AND TR%(i)d.content_type_id = Rx.content_type_id' % {'i': i})
18 where.append('AND TR%d.tag_id = %d' % (i, tag.pk))
19 return " ".join([select] + joins + where)
23 def get_related_tags(tags):
24 # Get Tag fields for constructing tags in a raw query.
25 tag_fields = ('id', 'category', 'slug', 'sort_key', 'name_%s' % get_language())
26 tag_fields = ', '.join(
27 'T.%s' % connection.ops.quote_name(field)
28 for field in tag_fields)
29 tag_ids = tuple(t.pk for t in tags)
31 # This is based on fragments/areas sharing their works tags
32 qs = Tag.objects.raw('''
33 SELECT ''' + tag_fields + ''', COUNT(T.id) count
35 -- R: TagRelations of all objects tagged with the given tags.
37 ''' + _get_tag_relations_sql(tags) + '''
40 SELECT ''' + tag_fields + ''', MAX(R4.object_id) ancestor
44 -- R2: All tags of the found objects.
45 JOIN catalogue_tag_relation R2
46 ON R2.object_id = R1.object_id
47 AND R2.content_type_id = R1.content_type_id
49 -- Tag data for output.
53 -- Special case for books:
54 -- We want to exclude from output all the relations
55 -- between a book and a tag, if there's a relation between
56 -- the the book's ancestor and the tag in the result.
57 LEFT JOIN catalogue_book_ancestor A
58 ON A.from_book_id = R1.object_id
59 AND R1.content_type_id = %s
60 LEFT JOIN catalogue_tag_relation R3
61 ON R3.tag_id = R2.tag_id
62 AND R3.content_type_id = R1.content_type_id
63 AND R3.object_id = A.to_book_id
65 ON R4.object_id = R3.object_id
66 AND R4.content_type_id = R3.content_type_id
69 -- Exclude from the result the tags we started with.
71 -- Special case for books: exclude descendants.
72 -- AND R4.object_id IS NULL
74 -- Only count fragment tags on fragments
75 -- and book tags for books.
76 (R2.content_type_id IN %s AND T.category IN %s)
78 (R2.content_type_id IN %s AND T.category IN %s)
81 GROUP BY T.id, R2.object_id, R2.content_type_id
84 -- Now group by tag and count occurencies.
85 WHERE ancestor IS NULL
86 GROUP BY ''' + tag_fields + '''
89 ContentType.objects.get_for_model(Book).pk,
91 tuple(ContentType.objects.get_for_model(model).pk
92 for model in (Fragment, PictureArea)),
94 tuple(ContentType.objects.get_for_model(model).pk
95 for model in (Book, Picture)),
96 ('author', 'epoch', 'genre', 'kind'),
101 def get_fragment_related_tags(tags):
102 tag_fields = ', '.join(
103 'T.%s' % (connection.ops.quote_name(field.column))
104 for field in Tag._meta.fields)
106 tag_ids = tuple(t.pk for t in tags)
107 # This is based on fragments/areas sharing their works tags
108 return Tag.objects.raw('''
109 SELECT T.*, COUNT(T.id) count
114 -- R1: TagRelations of all objects tagged with the given tags.
116 ''' + _get_tag_relations_sql(tags) + '''
119 -- R2: All tags of the found objects.
120 JOIN catalogue_tag_relation R2
121 ON R2.object_id = R1.object_id
122 AND R2.content_type_id = R1.content_type_id
124 -- Tag data for output.
129 -- Exclude from the result the tags we started with.
131 GROUP BY T.id, R2.object_id, R2.content_type_id
134 -- Now group by tag and count occurencies.
135 GROUP BY ''' + tag_fields + '''
142 def tags_usage_for_books(categories):
143 tag_fields = ', '.join(
144 'T.%s' % (connection.ops.quote_name(field.column))
145 for field in Tag._meta.fields)
147 # This is based on fragments/areas sharing their works tags
148 return Tag.objects.raw('''
149 SELECT T.*, COUNT(T.id) count
153 FROM catalogue_tag_relation R1
155 -- Tag data for output.
159 -- We want to exclude from output all the relations
160 -- between a book and a tag, if there's a relation between
161 -- the the book's ancestor and the tag in the result.
162 LEFT JOIN catalogue_book_ancestor A
163 ON A.from_book_id=R1.object_id
164 LEFT JOIN catalogue_tag_relation R3
165 ON R3.tag_id = R1.tag_id
166 AND R3.content_type_id = R1.content_type_id
167 AND R3.object_id = A.to_book_id
170 R1.content_type_id = %s
171 -- Special case for books: exclude descendants.
172 AND R3.object_id IS NULL
176 -- Shouldn't it just be 'distinct'?
177 -- Maybe it's faster this way.
178 GROUP BY T.id, R1.object_id, R1.content_type_id
181 -- Now group by tag and count occurencies.
182 GROUP BY ''' + tag_fields + '''
185 ContentType.objects.get_for_model(Book).pk,
190 def tags_usage_for_works(categories):
191 tag_fields = ', '.join(
192 'T.%s' % (connection.ops.quote_name(field.column))
193 for field in Tag._meta.fields)
195 return Tag.objects.raw('''
196 SELECT T.*, COUNT(T.id) count
201 FROM catalogue_tag_relation R1
203 -- Tag data for output.
207 -- Special case for books:
208 -- We want to exclude from output all the relations
209 -- between a book and a tag, if there's a relation between
210 -- the the book's ancestor and the tag in the result.
211 LEFT JOIN catalogue_book_ancestor A
212 ON A.from_book_id = R1.object_id
213 AND R1.content_type_id = %s
214 LEFT JOIN catalogue_tag_relation R3
215 ON R3.tag_id = R1.tag_id
216 AND R3.content_type_id = R1.content_type_id
217 AND R3.object_id = A.to_book_id
220 R1.content_type_id IN %s
221 -- Special case for books: exclude descendants.
222 AND R3.object_id IS NULL
226 -- Shouldn't it just be 'distinct'?
227 -- Maybe it's faster this way.
228 GROUP BY T.id, R1.object_id, R1.content_type_id
231 -- Now group by tag and count occurencies.
232 GROUP BY ''' + tag_fields + '''
236 ContentType.objects.get_for_model(Book).pk,
237 tuple(ContentType.objects.get_for_model(model).pk for model in (Book, Picture)),
242 def tags_usage_for_fragments(categories):
243 return Tag.objects.raw('''
244 SELECT t.*, count(t.id)
245 from catalogue_tag_relation r
248 where t.category IN %s