##// END OF EJS Templates
Speed up tag local cache
neko259 -
r2021:b8cdd5b1 default
parent child Browse files
Show More
@@ -1,212 +1,212 b''
1 import hashlib
1 import hashlib
2 from django.urls import reverse
2 from django.urls import reverse
3 from django.db import models
3 from django.db import models
4 from django.db.models import Count, Q
4 from django.db.models import Count, Q
5 from django.utils.translation import get_language
5 from django.utils.translation import get_language
6
6
7 import boards
7 import boards
8 from boards.models import Attachment
8 from boards.models import Attachment
9 from boards.models.attachment import FILE_TYPES_IMAGE
9 from boards.models.attachment import FILE_TYPES_IMAGE
10 from boards.models.base import Viewable
10 from boards.models.base import Viewable
11 from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE
11 from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE
12 from boards.utils import cached_result
12 from boards.utils import cached_result
13
13
14 __author__ = 'neko259'
14 __author__ = 'neko259'
15
15
16
16
17 RELATED_TAGS_COUNT = 5
17 RELATED_TAGS_COUNT = 5
18 DEFAULT_LOCALE = 'default'
18 DEFAULT_LOCALE = 'default'
19
19
20
20
21 class TagAliasManager(models.Manager):
21 class TagAliasManager(models.Manager):
22 def filter_localized(self, *args, **kwargs):
22 def filter_localized(self, *args, **kwargs):
23 locale = get_language()
23 locale = get_language()
24 tag_aliases = (self.filter(locale=locale)
24 tag_aliases = (self.filter(locale=locale)
25 | self.filter(Q(locale=DEFAULT_LOCALE)
25 | self.filter(Q(locale=DEFAULT_LOCALE)
26 & ~Q(parent__aliases__locale=locale)))
26 & ~Q(parent__aliases__locale=locale)))
27 return tag_aliases.filter(**kwargs)
27 return tag_aliases.filter(**kwargs)
28
28
29
29
30 class TagAlias(models.Model, Viewable):
30 class TagAlias(models.Model, Viewable):
31 class Meta:
31 class Meta:
32 app_label = 'boards'
32 app_label = 'boards'
33 ordering = ('name',)
33 ordering = ('name',)
34
34
35 objects = TagAliasManager()
35 objects = TagAliasManager()
36
36
37 name = models.CharField(max_length=100, db_index=True)
37 name = models.CharField(max_length=100, db_index=True)
38 locale = models.CharField(max_length=10, db_index=True)
38 locale = models.CharField(max_length=10, db_index=True)
39
39
40 parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True,
40 parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True,
41 blank=True, related_name='aliases')
41 blank=True, related_name='aliases')
42
42
43
43
44 class TagManager(models.Manager):
44 class TagManager(models.Manager):
45 def get_not_empty_tags(self):
45 def get_not_empty_tags(self):
46 """
46 """
47 Gets tags that have non-archived threads.
47 Gets tags that have non-archived threads.
48 """
48 """
49
49
50 return self.annotate(num_threads=Count('thread_tags'))\
50 return self.annotate(num_threads=Count('thread_tags'))\
51 .filter(num_threads__gt=0)\
51 .filter(num_threads__gt=0)\
52 .filter(aliases__in=TagAlias.objects.filter_localized())\
52 .filter(aliases__in=TagAlias.objects.filter_localized())\
53 .order_by('aliases__name')
53 .order_by('aliases__name')
54
54
55 def get_tag_url_list(self, tags: list) -> str:
55 def get_tag_url_list(self, tags: list) -> str:
56 """
56 """
57 Gets a comma-separated list of tag links.
57 Gets a comma-separated list of tag links.
58 """
58 """
59
59
60 return ', '.join([tag.get_view() for tag in tags])
60 return ', '.join([tag.get_view() for tag in tags])
61
61
62 def get_by_alias(self, alias):
62 def get_by_alias(self, alias):
63 tag = None
63 tag = None
64 aliases = TagAlias.objects.filter(name=alias).all()
64 aliases = TagAlias.objects.filter(name=alias).all()
65 if aliases:
65 if aliases:
66 tag = aliases[0].parent
66 tag = aliases[0].parent
67
67
68 return tag
68 return tag
69
69
70 def get_or_create_with_alias(self, name, required=False):
70 def get_or_create_with_alias(self, name, required=False):
71 tag = self.get_by_alias(name)
71 tag = self.get_by_alias(name)
72 created = False
72 created = False
73 if not tag:
73 if not tag:
74 tag = self.create(required=required)
74 tag = self.create(required=required)
75 TagAlias.objects.create(name=name, locale=DEFAULT_LOCALE, parent=tag)
75 TagAlias.objects.create(name=name, locale=DEFAULT_LOCALE, parent=tag)
76 created = True
76 created = True
77 return tag, created
77 return tag, created
78
78
79
79
80 class Tag(models.Model, Viewable):
80 class Tag(models.Model, Viewable):
81 """
81 """
82 A tag is a text node assigned to the thread. The tag serves as a board
82 A tag is a text node assigned to the thread. The tag serves as a board
83 section. There can be multiple tags for each thread
83 section. There can be multiple tags for each thread
84 """
84 """
85
85
86 objects = TagManager()
86 objects = TagManager()
87
87
88 class Meta:
88 class Meta:
89 app_label = 'boards'
89 app_label = 'boards'
90
90
91 required = models.BooleanField(default=False, db_index=True)
91 required = models.BooleanField(default=False, db_index=True)
92 description = models.TextField(blank=True)
92 description = models.TextField(blank=True)
93
93
94 parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True,
94 parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True,
95 blank=True, related_name='children')
95 blank=True, related_name='children')
96
96
97 def get_name(self):
97 def get_name(self):
98 return self.aliases.get(locale=DEFAULT_LOCALE).name
98 return self.aliases.get(locale=DEFAULT_LOCALE).name
99
99
100 def __str__(self):
100 def __str__(self):
101 return self.get_name()
101 return self.get_name()
102
102
103 def is_empty(self) -> bool:
103 def is_empty(self) -> bool:
104 """
104 """
105 Checks if the tag has some threads.
105 Checks if the tag has some threads.
106 """
106 """
107
107
108 return self.get_thread_count() == 0
108 return self.get_thread_count() == 0
109
109
110 def get_thread_count(self, status=None) -> int:
110 def get_thread_count(self, status=None) -> int:
111 threads = self.get_threads()
111 threads = self.get_threads()
112 if status is not None:
112 if status is not None:
113 threads = threads.filter(status=status)
113 threads = threads.filter(status=status)
114 return threads.count()
114 return threads.count()
115
115
116 def get_active_thread_count(self) -> int:
116 def get_active_thread_count(self) -> int:
117 return self.get_thread_count(status=STATUS_ACTIVE)
117 return self.get_thread_count(status=STATUS_ACTIVE)
118
118
119 def get_bumplimit_thread_count(self) -> int:
119 def get_bumplimit_thread_count(self) -> int:
120 return self.get_thread_count(status=STATUS_BUMPLIMIT)
120 return self.get_thread_count(status=STATUS_BUMPLIMIT)
121
121
122 def get_archived_thread_count(self) -> int:
122 def get_archived_thread_count(self) -> int:
123 return self.get_thread_count(status=STATUS_ARCHIVE)
123 return self.get_thread_count(status=STATUS_ARCHIVE)
124
124
125 @cached_result()
125 @cached_result()
126 def get_absolute_url(self):
126 def get_absolute_url(self):
127 return reverse('tag', kwargs={'tag_name': self.get_name()})
127 return reverse('tag', kwargs={'tag_name': self.get_name()})
128
128
129 def get_threads(self):
129 def get_threads(self):
130 return self.thread_tags.order_by('-bump_time')
130 return self.thread_tags.order_by('-bump_time')
131
131
132 def is_required(self):
132 def is_required(self):
133 return self.required
133 return self.required
134
134
135 def _get_locale_cache_key(self):
135 def _get_locale_cache_key(self):
136 return '{}_{}'.format(self.id, get_language())
136 return [get_language()]
137
137
138 @cached_result(key_method=_get_locale_cache_key)
138 @cached_result(key_method=_get_locale_cache_key)
139 def get_localized_name(self):
139 def get_localized_name(self):
140 locale = get_language()
140 locale = get_language()
141
141
142 aliases = self.aliases.filter(Q(locale=locale) | Q(locale=DEFAULT_LOCALE))
142 aliases = self.aliases.filter(Q(locale=locale) | Q(locale=DEFAULT_LOCALE))
143
143
144 localized_tag_name = None
144 localized_tag_name = None
145 default_tag_name = None
145 default_tag_name = None
146
146
147 for alias in aliases:
147 for alias in aliases:
148 if alias.locale == locale:
148 if alias.locale == locale:
149 localized_tag_name = alias.name
149 localized_tag_name = alias.name
150 elif alias.locale == DEFAULT_LOCALE:
150 elif alias.locale == DEFAULT_LOCALE:
151 default_tag_name = alias.name
151 default_tag_name = alias.name
152
152
153 return localized_tag_name if localized_tag_name else default_tag_name
153 return localized_tag_name if localized_tag_name else default_tag_name
154
154
155 def get_view(self):
155 def get_view(self):
156 name = self.get_localized_name()
156 name = self.get_localized_name()
157 link = '<a class="tag" href="{}">{}</a>'.format(
157 link = '<a class="tag" href="{}">{}</a>'.format(
158 self.get_absolute_url(), name)
158 self.get_absolute_url(), name)
159 if self.is_required():
159 if self.is_required():
160 link = '<b>{}</b>'.format(link)
160 link = '<b>{}</b>'.format(link)
161 return link
161 return link
162
162
163 @cached_result()
163 @cached_result()
164 def get_post_count(self):
164 def get_post_count(self):
165 return self.get_threads().aggregate(num_posts=Count('replies'))['num_posts']
165 return self.get_threads().aggregate(num_posts=Count('replies'))['num_posts']
166
166
167 def get_description(self):
167 def get_description(self):
168 return self.description
168 return self.description
169
169
170 def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]):
170 def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]):
171 posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\
171 posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\
172 .annotate(images_count=Count(
172 .annotate(images_count=Count(
173 'attachments')).filter(images_count__gt=0, thread__tags__in=[self])
173 'attachments')).filter(images_count__gt=0, thread__tags__in=[self])
174 if status is not None:
174 if status is not None:
175 posts = posts.filter(thread__status__in=status)
175 posts = posts.filter(thread__status__in=status)
176 return posts.order_by('?').first()
176 return posts.order_by('?').first()
177
177
178 def get_first_letter(self):
178 def get_first_letter(self):
179 name = self.get_localized_name()
179 name = self.get_localized_name()
180 return name and name[0] or ''
180 return name and name[0] or ''
181
181
182 def get_related_tags(self):
182 def get_related_tags(self):
183 return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
183 return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
184 id=self.id).order_by('?')[:RELATED_TAGS_COUNT])
184 id=self.id).order_by('?')[:RELATED_TAGS_COUNT])
185
185
186 @cached_result()
186 @cached_result()
187 def get_color(self):
187 def get_color(self):
188 """
188 """
189 Gets color hashed from the tag name.
189 Gets color hashed from the tag name.
190 """
190 """
191 return hashlib.md5(self.get_name().encode()).hexdigest()[:6]
191 return hashlib.md5(self.get_name().encode()).hexdigest()[:6]
192
192
193 def get_parent(self):
193 def get_parent(self):
194 return self.parent
194 return self.parent
195
195
196 def get_all_parents(self):
196 def get_all_parents(self):
197 parents = list()
197 parents = list()
198 parent = self.get_parent()
198 parent = self.get_parent()
199 if parent and parent not in parents:
199 if parent and parent not in parents:
200 parents.insert(0, parent)
200 parents.insert(0, parent)
201 parents = parent.get_all_parents() + parents
201 parents = parent.get_all_parents() + parents
202
202
203 return parents
203 return parents
204
204
205 def get_children(self):
205 def get_children(self):
206 return self.children
206 return self.children
207
207
208 def get_images(self):
208 def get_images(self):
209 return Attachment.objects.filter(
209 return Attachment.objects.filter(
210 attachment_posts__thread__tags__in=[self]).filter(
210 attachment_posts__thread__tags__in=[self]).filter(
211 mimetype__in=FILE_TYPES_IMAGE).order_by('-attachment_posts__pub_time')
211 mimetype__in=FILE_TYPES_IMAGE).order_by('-attachment_posts__pub_time')
212
212
General Comments 0
You need to be logged in to leave comments. Login now