tag.py
211 lines
| 6.3 KiB
| text/x-python
|
PythonLexer
neko259
|
r1338 | import hashlib | ||
neko259
|
r1986 | from django.urls import reverse | ||
neko259
|
r386 | from django.db import models | ||
neko259
|
r1889 | from django.db.models import Count, Q | ||
neko259
|
r1860 | from django.utils.translation import get_language | ||
neko259
|
r732 | |||
neko259
|
r1946 | import boards | ||
neko259
|
r1590 | from boards.models import Attachment | ||
neko259
|
r1946 | from boards.models.attachment import FILE_TYPES_IMAGE | ||
neko259
|
r692 | from boards.models.base import Viewable | ||
neko259
|
r1414 | from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE | ||
neko259
|
r973 | from boards.utils import cached_result | ||
neko259
|
r732 | |||
neko259
|
r2022 | |||
neko259
|
r385 | __author__ = 'neko259' | ||
neko259
|
r2022 | TAG_DELIMITER = ', ' | ||
neko259
|
r1270 | RELATED_TAGS_COUNT = 5 | ||
neko259
|
r1874 | DEFAULT_LOCALE = 'default' | ||
neko259
|
r1270 | |||
neko259
|
r1862 | |||
neko259
|
r1891 | class TagAliasManager(models.Manager): | ||
def filter_localized(self, *args, **kwargs): | ||||
locale = get_language() | ||||
tag_aliases = (self.filter(locale=locale) | ||||
| self.filter(Q(locale=DEFAULT_LOCALE) | ||||
& ~Q(parent__aliases__locale=locale))) | ||||
return tag_aliases.filter(**kwargs) | ||||
neko259
|
r1862 | class TagAlias(models.Model, Viewable): | ||
class Meta: | ||||
app_label = 'boards' | ||||
ordering = ('name',) | ||||
neko259
|
r2023 | unique_together = ('name', 'locale') | ||
neko259
|
r1862 | |||
neko259
|
r1891 | objects = TagAliasManager() | ||
neko259
|
r1873 | name = models.CharField(max_length=100, db_index=True) | ||
neko259
|
r1862 | locale = models.CharField(max_length=10, db_index=True) | ||
neko259
|
r1986 | parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True, | ||
blank=True, related_name='aliases') | ||||
neko259
|
r1860 | |||
neko259
|
r2023 | def __str__(self): | ||
return self.name | ||||
neko259
|
r1270 | |||
neko259
|
r385 | class TagManager(models.Manager): | ||
neko259
|
r1027 | def get_tag_url_list(self, tags: list) -> str: | ||
""" | ||||
Gets a comma-separated list of tag links. | ||||
""" | ||||
neko259
|
r2022 | return TAG_DELIMITER.join([tag.get_view() for tag in tags]) | ||
neko259
|
r1027 | |||
neko259
|
r1860 | def get_by_alias(self, alias): | ||
neko259
|
r2022 | try: | ||
neko259
|
r2023 | return self.filter(aliases__name__in=[alias]).first() | ||
neko259
|
r2022 | except Tag.DoesNotExist: | ||
pass | ||||
neko259
|
r1860 | |||
neko259
|
r1874 | def get_or_create_with_alias(self, name, required=False): | ||
tag = self.get_by_alias(name) | ||||
created = False | ||||
if not tag: | ||||
tag = self.create(required=required) | ||||
neko259
|
r1883 | TagAlias.objects.create(name=name, locale=DEFAULT_LOCALE, parent=tag) | ||
neko259
|
r1874 | created = True | ||
return tag, created | ||||
neko259
|
r385 | |||
neko259
|
r692 | class Tag(models.Model, Viewable): | ||
neko259
|
r385 | """ | ||
neko259
|
r398 | A tag is a text node assigned to the thread. The tag serves as a board | ||
section. There can be multiple tags for each thread | ||||
neko259
|
r385 | """ | ||
objects = TagManager() | ||||
class Meta: | ||||
app_label = 'boards' | ||||
neko259
|
r983 | required = models.BooleanField(default=False, db_index=True) | ||
neko259
|
r1258 | description = models.TextField(blank=True) | ||
neko259
|
r385 | |||
neko259
|
r1986 | parent = models.ForeignKey('Tag', on_delete=models.CASCADE, null=True, | ||
blank=True, related_name='children') | ||||
neko259
|
r1348 | |||
neko259
|
r1874 | def get_name(self): | ||
neko259
|
r2033 | try: | ||
alias = self.aliases.get(locale=DEFAULT_LOCALE) | ||||
except TagAlias.DoesNotExist: | ||||
alias = self.aliases.first() | ||||
if alias: | ||||
return alias.name | ||||
else: | ||||
# This is an extremely bad situation, somehow we have a tag without | ||||
# any alias | ||||
return 'BROKEN #{}'.format(self.id) | ||||
neko259
|
r1874 | |||
neko259
|
r875 | def __str__(self): | ||
neko259
|
r1874 | return self.get_name() | ||
neko259
|
r385 | |||
neko259
|
r908 | def is_empty(self) -> bool: | ||
neko259
|
r623 | """ | ||
Checks if the tag has some threads. | ||||
""" | ||||
neko259
|
r606 | return self.get_thread_count() == 0 | ||
neko259
|
r385 | |||
neko259
|
r1414 | def get_thread_count(self, status=None) -> int: | ||
neko259
|
r1260 | threads = self.get_threads() | ||
neko259
|
r1414 | if status is not None: | ||
threads = threads.filter(status=status) | ||||
neko259
|
r1260 | return threads.count() | ||
def get_active_thread_count(self) -> int: | ||||
neko259
|
r1414 | return self.get_thread_count(status=STATUS_ACTIVE) | ||
neko259
|
r1383 | |||
def get_bumplimit_thread_count(self) -> int: | ||||
neko259
|
r1414 | return self.get_thread_count(status=STATUS_BUMPLIMIT) | ||
neko259
|
r385 | |||
neko259
|
r1364 | def get_archived_thread_count(self) -> int: | ||
neko259
|
r1414 | return self.get_thread_count(status=STATUS_ARCHIVE) | ||
neko259
|
r1364 | |||
neko259
|
r1897 | @cached_result() | ||
neko259
|
r1160 | def get_absolute_url(self): | ||
neko259
|
r1874 | return reverse('tag', kwargs={'tag_name': self.get_name()}) | ||
neko259
|
r692 | |||
neko259
|
r908 | def get_threads(self): | ||
neko259
|
r1269 | return self.thread_tags.order_by('-bump_time') | ||
neko259
|
r922 | |||
def is_required(self): | ||||
return self.required | ||||
neko259
|
r1888 | def _get_locale_cache_key(self): | ||
neko259
|
r2021 | return [get_language()] | ||
neko259
|
r1888 | |||
@cached_result(key_method=_get_locale_cache_key) | ||||
neko259
|
r1892 | def get_localized_name(self): | ||
neko259
|
r1860 | locale = get_language() | ||
neko259
|
r1889 | aliases = self.aliases.filter(Q(locale=locale) | Q(locale=DEFAULT_LOCALE)) | ||
localized_tag_name = None | ||||
default_tag_name = None | ||||
neko259
|
r1860 | |||
neko259
|
r1889 | for alias in aliases: | ||
if alias.locale == locale: | ||||
localized_tag_name = alias.name | ||||
elif alias.locale == DEFAULT_LOCALE: | ||||
default_tag_name = alias.name | ||||
neko259
|
r1874 | |||
neko259
|
r2022 | return localized_tag_name or default_tag_name | ||
neko259
|
r1892 | |||
def get_view(self): | ||||
name = self.get_localized_name() | ||||
neko259
|
r922 | link = '<a class="tag" href="{}">{}</a>'.format( | ||
neko259
|
r1861 | self.get_absolute_url(), name) | ||
neko259
|
r922 | if self.is_required(): | ||
link = '<b>{}</b>'.format(link) | ||||
return link | ||||
neko259
|
r1107 | @cached_result() | ||
neko259
|
r960 | def get_post_count(self): | ||
neko259
|
r1704 | return self.get_threads().aggregate(num_posts=Count('replies'))['num_posts'] | ||
neko259
|
r1258 | |||
def get_description(self): | ||||
return self.description | ||||
neko259
|
r1264 | |||
neko259
|
r1417 | def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]): | ||
neko259
|
r1590 | posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\ | ||
.annotate(images_count=Count( | ||||
neko259
|
r1704 | 'attachments')).filter(images_count__gt=0, thread__tags__in=[self]) | ||
neko259
|
r1414 | if status is not None: | ||
neko259
|
r1417 | posts = posts.filter(thread__status__in=status) | ||
neko259
|
r1265 | return posts.order_by('?').first() | ||
neko259
|
r1264 | |||
neko259
|
r1269 | def get_related_tags(self): | ||
neko259
|
r1270 | return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude( | ||
neko259
|
r1338 | id=self.id).order_by('?')[:RELATED_TAGS_COUNT]) | ||
@cached_result() | ||||
def get_color(self): | ||||
""" | ||||
Gets color hashed from the tag name. | ||||
""" | ||||
neko259
|
r1874 | return hashlib.md5(self.get_name().encode()).hexdigest()[:6] | ||
neko259
|
r1348 | |||
def get_parent(self): | ||||
return self.parent | ||||
def get_all_parents(self): | ||||
neko259
|
r1361 | parents = list() | ||
neko259
|
r1348 | parent = self.get_parent() | ||
if parent and parent not in parents: | ||||
neko259
|
r1361 | parents.insert(0, parent) | ||
parents = parent.get_all_parents() + parents | ||||
neko259
|
r1348 | |||
return parents | ||||
def get_children(self): | ||||
return self.children | ||||
neko259
|
r1419 | |||
def get_images(self): | ||||
neko259
|
r1590 | return Attachment.objects.filter( | ||
neko259
|
r1591 | attachment_posts__thread__tags__in=[self]).filter( | ||
neko259
|
r1729 | mimetype__in=FILE_TYPES_IMAGE).order_by('-attachment_posts__pub_time') | ||