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