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