Fixes #4076: Recommendations from collections.
[wolnelektury.git] / src / social / utils.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from collections import defaultdict
5 from random import randint
6
7 from django.contrib.contenttypes.models import ContentType
8 from django.db.models import Q
9 from django.utils.functional import lazy
10 from catalogue.models import Book, Tag
11 from catalogue import utils
12 from catalogue.tasks import touch_tag
13 from social.models import Cite
14
15
16 def likes(user, work, request=None):
17     if not user.is_authenticated:
18         return False
19
20     if request is None:
21         return work.tags.filter(category='set', user=user).exists()
22
23     if not hasattr(request, 'social_likes'):
24         # tuple: unchecked, checked, liked
25         request.social_likes = defaultdict(lambda: (set(), set(), set()))
26
27     ct = ContentType.objects.get_for_model(type(work))
28     likes_t = request.social_likes[ct.pk]
29     if work.pk in likes_t[1]:
30         return work.pk in likes_t[2]
31     else:
32         likes_t[0].add(work.pk)
33
34         def _likes():
35             if likes_t[0]:
36                 ids = tuple(likes_t[0])
37                 likes_t[0].clear()
38                 likes_t[2].update(Tag.intermediary_table_model.objects.filter(
39                     content_type_id=ct.pk, tag__user_id=user.pk,
40                     object_id__in=ids
41                 ).distinct().values_list('object_id', flat=True))
42                 likes_t[1].update(ids)
43             return work.pk in likes_t[2]
44         return lazy(_likes, bool)()
45
46
47 def get_set(user, name):
48     """Returns a tag for use by the user. Creates it, if necessary."""
49     try:
50         tag = Tag.objects.get(category='set', user=user, name=name)
51     except Tag.DoesNotExist:
52         tag = Tag.objects.create(
53             category='set', user=user, name=name, slug=utils.get_random_hash(name), sort_key=name.lower())
54     except Tag.MultipleObjectsReturned:
55         # fix duplicated noname shelf
56         tags = list(Tag.objects.filter(category='set', user=user, name=name))
57         tag = tags[0]
58         for other_tag in tags[1:]:
59             for item in other_tag.items.all():
60                 Tag.objects.remove_tag(item, other_tag)
61                 Tag.objects.add_tag(item, tag)
62             other_tag.delete()
63     return tag
64
65
66 def set_sets(user, work, sets):
67     """Set tags used for given work by a given user."""
68
69     old_sets = list(work.tags.filter(category='set', user=user))
70
71     work.tags = sets + list(
72             work.tags.filter(~Q(category='set') | ~Q(user=user)))
73
74     for shelf in [shelf for shelf in old_sets if shelf not in sets]:
75         touch_tag(shelf)
76     for shelf in [shelf for shelf in sets if shelf not in old_sets]:
77         touch_tag(shelf)
78
79     # delete empty tags
80     Tag.objects.filter(category='set', user=user, items=None).delete()
81
82     if isinstance(work, Book):
83         work.update_popularity()
84
85
86 def cites_for_tags(tags):
87     """Returns a QuerySet with all Cites for books with given tags."""
88     return Cite.objects.filter(book__in=Book.tagged.with_all(tags))
89
90
91 # tag_ids is never used
92 def choose_cite(book_id=None, tag_ids=None):
93     """Choose a cite for main page, for book or for set of tags."""
94     if book_id is not None:
95         cites = Cite.objects.filter(Q(book=book_id) | Q(book__ancestor=book_id))
96     elif tag_ids is not None:
97         tags = Tag.objects.filter(pk__in=tag_ids)
98         cites = cites_for_tags(tags)
99     else:
100         cites = Cite.objects.all()
101     stickies = cites.filter(sticky=True)
102     count = len(stickies)
103     if count:
104         cites = stickies
105     else:
106         count = len(cites)
107     if count:
108         cite = cites[randint(0, count - 1)]
109     else:
110         cite = None
111     return cite
112
113
114 def get_or_choose_cite(request, book_id=None, tag_ids=None):
115     try:
116         assert request.user.is_staff
117         assert 'banner' in request.GET
118         return Cite.objects.get(pk=request.GET['banner'])
119     except (AssertionError, Cite.DoesNotExist):
120         return choose_cite(book_id, tag_ids)