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