446 lines
13 KiB
Python
446 lines
13 KiB
Python
"""Utils app models."""
|
|
import logging
|
|
from os.path import exists
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
|
from django.contrib.gis.db import models
|
|
from django.contrib.postgres.aggregates import ArrayAgg
|
|
from django.contrib.postgres.fields import JSONField
|
|
from django.contrib.postgres.fields.jsonb import KeyTextTransform
|
|
from django.utils import timezone
|
|
from django.utils.html import mark_safe
|
|
from django.utils.translation import ugettext_lazy as _, get_language
|
|
from configuration.models import TranslationSettings
|
|
from easy_thumbnails.fields import ThumbnailerImageField
|
|
from sorl.thumbnail import get_thumbnail
|
|
from sorl.thumbnail.fields import ImageField as SORLImageField
|
|
|
|
from utils.methods import image_path, svg_image_path
|
|
from utils.validators import svg_image_validator
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProjectBaseMixin(models.Model):
|
|
"""Base mixin model."""
|
|
|
|
created = models.DateTimeField(default=timezone.now, editable=False,
|
|
verbose_name=_('Date created'))
|
|
modified = models.DateTimeField(auto_now=True,
|
|
verbose_name=_('Date updated'))
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
abstract = True
|
|
|
|
|
|
def valid(value):
|
|
print("Run")
|
|
|
|
|
|
class TJSONField(JSONField):
|
|
"""Overrided JsonField."""
|
|
|
|
|
|
def to_locale(language):
|
|
"""Turn a language name (en-us) into a locale name (en_US)."""
|
|
if language:
|
|
language, _, country = language.lower().partition('-')
|
|
if not country:
|
|
return language
|
|
# A language with > 2 characters after the dash only has its first
|
|
# character after the dash capitalized; e.g. sr-latn becomes sr-Latn.
|
|
# A language with 2 characters after the dash has both characters
|
|
# capitalized; e.g. en-us becomes en-US.
|
|
country, _, tail = country.partition('-')
|
|
country = country.title() if len(country) > 2 else country.upper()
|
|
if tail:
|
|
country += '-' + tail
|
|
return language + '-' + country
|
|
|
|
|
|
def get_current_locale():
|
|
"""Get current language."""
|
|
return to_locale(get_language())
|
|
|
|
|
|
def get_default_locale():
|
|
return TranslationSettings.get_solo().default_language or \
|
|
settings.FALLBACK_LOCALE
|
|
|
|
|
|
def translate_field(self, field_name):
|
|
def translate(self):
|
|
field = getattr(self, field_name)
|
|
if isinstance(field, dict):
|
|
value = field.get(to_locale(get_language()))
|
|
# fallback
|
|
if value is None:
|
|
value = field.get(get_default_locale())
|
|
if value is None:
|
|
value = field.get(next(iter(field.keys()), None))
|
|
return value
|
|
return None
|
|
return translate
|
|
|
|
|
|
# todo: refactor this
|
|
class IndexJSON:
|
|
|
|
def __getattr__(self, item):
|
|
return None
|
|
|
|
|
|
def index_field(self, field_name):
|
|
def index(self):
|
|
field = getattr(self, field_name)
|
|
obj = IndexJSON()
|
|
if isinstance(field, dict):
|
|
for key, value in field.items():
|
|
setattr(obj, key, value)
|
|
return obj
|
|
|
|
return index
|
|
|
|
|
|
class TranslatedFieldsMixin:
|
|
"""Translated Fields mixin"""
|
|
|
|
STR_FIELD_NAME = ''
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Overrided __init__ method."""
|
|
super(TranslatedFieldsMixin, self).__init__(*args, **kwargs)
|
|
cls = self.__class__
|
|
for field in cls._meta.fields:
|
|
field_name = field.name
|
|
if isinstance(field, TJSONField):
|
|
setattr(cls, f'{field.name}_translated',
|
|
property(translate_field(self, field_name)))
|
|
setattr(cls, f'{field_name}_indexing',
|
|
property(index_field(self, field_name)))
|
|
|
|
def __str__(self):
|
|
"""Overrided __str__ method."""
|
|
value = None
|
|
if self.STR_FIELD_NAME:
|
|
field = getattr(self, getattr(self, 'STR_FIELD_NAME'))
|
|
if isinstance(field, dict):
|
|
value = field.get(get_current_locale())
|
|
return value if value else super(TranslatedFieldsMixin, self).__str__()
|
|
|
|
|
|
class OAuthProjectMixin:
|
|
"""OAuth2 mixin for project GM"""
|
|
|
|
def get_source(self):
|
|
"""Method to get of platform"""
|
|
return NotImplementedError
|
|
|
|
|
|
class BaseAttributes(ProjectBaseMixin):
|
|
created_by = models.ForeignKey(
|
|
'account.User', on_delete=models.SET_NULL, verbose_name=_('created by'),
|
|
null=True, related_name='%(class)s_records_created'
|
|
)
|
|
modified_by = models.ForeignKey(
|
|
'account.User', on_delete=models.SET_NULL, verbose_name=_('modified by'),
|
|
null=True, related_name='%(class)s_records_modified'
|
|
)
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
abstract = True
|
|
|
|
|
|
class ImageMixin(models.Model):
|
|
"""Avatar model."""
|
|
|
|
THUMBNAIL_KEY = 'default'
|
|
|
|
image = ThumbnailerImageField(upload_to=image_path,
|
|
blank=True, null=True, default=None,
|
|
verbose_name=_('Image'))
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
abstract = True
|
|
|
|
def get_image(self, key=None):
|
|
"""Get thumbnailed image file."""
|
|
return self.image[key or self.THUMBNAIL_KEY] if self.image else None
|
|
|
|
def get_image_url(self, key=None):
|
|
"""Get image thumbnail url."""
|
|
return self.get_image(key).url if self.image else None
|
|
|
|
def image_tag(self):
|
|
"""Admin preview tag."""
|
|
if self.image:
|
|
return mark_safe('<img src="%s" />' % self.get_image_url())
|
|
else:
|
|
return None
|
|
|
|
def get_full_image_url(self, request, thumbnail_key=None):
|
|
"""Get full image url"""
|
|
if self.image and exists(self.image.path):
|
|
return request.build_absolute_uri(self.get_image(thumbnail_key).url)
|
|
else:
|
|
return None
|
|
|
|
image_tag.short_description = _('Image')
|
|
image_tag.allow_tags = True
|
|
|
|
|
|
class SORLImageMixin(models.Model):
|
|
"""Abstract model for SORL ImageField"""
|
|
|
|
image = SORLImageField(upload_to=image_path,
|
|
blank=True, null=True, default=None,
|
|
verbose_name=_('Image'))
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
abstract = True
|
|
|
|
def get_image(self, thumbnail_key: str):
|
|
"""Get thumbnail image file."""
|
|
if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES:
|
|
return get_thumbnail(
|
|
file_=self.image,
|
|
**settings.SORL_THUMBNAIL_ALIASES[thumbnail_key])
|
|
|
|
def get_image_url(self, thumbnail_key: str):
|
|
"""Get image thumbnail url."""
|
|
crop_image = self.get_image(thumbnail_key)
|
|
if hasattr(crop_image, 'url'):
|
|
return self.get_image(thumbnail_key).url
|
|
|
|
def image_tag(self):
|
|
"""Admin preview tag."""
|
|
if self.image:
|
|
return mark_safe(f'<img src="{self.image.url}" style="max-height: 25%; max-width: 25%" />')
|
|
else:
|
|
return None
|
|
|
|
image_tag.short_description = _('Image')
|
|
image_tag.allow_tags = True
|
|
|
|
|
|
class SVGImageMixin(models.Model):
|
|
"""SVG image model."""
|
|
|
|
svg_image = models.FileField(upload_to=svg_image_path,
|
|
blank=True, null=True, default=None,
|
|
validators=[svg_image_validator, ],
|
|
verbose_name=_('SVG image'))
|
|
|
|
@property
|
|
def svg_image_indexing(self):
|
|
return self.svg_image.url if self.svg_image else None
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
class URLImageMixin(models.Model):
|
|
"""URl for image and special methods."""
|
|
image_url = models.URLField(verbose_name=_('Image URL path'),
|
|
blank=True, null=True, default=None)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def image_tag(self):
|
|
if self.image_url:
|
|
return mark_safe(
|
|
f'<a href="{self.image_url}"><img src="{self.image_url}" height="50"/>')
|
|
else:
|
|
return None
|
|
|
|
image_tag.short_description = _('Image')
|
|
image_tag.allow_tags = True
|
|
|
|
|
|
class PlatformMixin(models.Model):
|
|
"""Platforms"""
|
|
|
|
MOBILE = 0
|
|
WEB = 1
|
|
ALL = 2
|
|
|
|
SOURCES = (
|
|
(MOBILE, _('Mobile')),
|
|
(WEB, _('Web')),
|
|
(ALL, _('All'))
|
|
)
|
|
source = models.PositiveSmallIntegerField(choices=SOURCES, default=MOBILE,
|
|
verbose_name=_('Source'))
|
|
|
|
class Meta:
|
|
"""Meta class"""
|
|
abstract = True
|
|
|
|
|
|
class LocaleManagerMixin(models.Manager):
|
|
"""Manager for locale"""
|
|
|
|
def annotate_localized_fields(self, locale):
|
|
"""Return queryset by locale"""
|
|
prefix = 'trans'
|
|
|
|
# Prepare fields to localization (only JSONField can be localized)
|
|
fields = [field.name for field in self.model._meta.fields if isinstance(field, JSONField)]
|
|
|
|
# Check filters to check if field has localization
|
|
filters = {f'{field}__has_key': locale for field in fields}
|
|
# Filter QuerySet by prepared filters
|
|
queryset = self.filter(**filters)
|
|
|
|
# Prepare field for annotator
|
|
localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in
|
|
fields}
|
|
|
|
# Annotate them
|
|
for _ in fields:
|
|
queryset = queryset.annotate(**localized_fields)
|
|
return queryset
|
|
|
|
|
|
class GMTokenGenerator(PasswordResetTokenGenerator):
|
|
CHANGE_EMAIL = 0
|
|
RESET_PASSWORD = 1
|
|
CHANGE_PASSWORD = 2
|
|
CONFIRM_EMAIL = 3
|
|
|
|
TOKEN_CHOICES = (
|
|
CHANGE_EMAIL,
|
|
RESET_PASSWORD,
|
|
CHANGE_PASSWORD,
|
|
CONFIRM_EMAIL
|
|
)
|
|
|
|
def __init__(self, purpose: int):
|
|
if purpose in self.TOKEN_CHOICES:
|
|
self.purpose = purpose
|
|
|
|
def get_fields(self, user, timestamp):
|
|
"""
|
|
Get user fields for hash value.
|
|
"""
|
|
fields = [str(timestamp), str(user.is_active), str(user.pk)]
|
|
if self.purpose == self.CHANGE_EMAIL or \
|
|
self.purpose == self.CONFIRM_EMAIL:
|
|
fields.extend([str(user.email_confirmed), str(user.email)])
|
|
elif self.purpose == self.RESET_PASSWORD or \
|
|
self.purpose == self.CHANGE_PASSWORD:
|
|
fields.append(str(user.password))
|
|
return fields
|
|
|
|
def _make_hash_value(self, user, timestamp):
|
|
"""
|
|
Hash the user's primary key and some user state that's sure to change
|
|
after a password reset to produce a token that invalidated when it's
|
|
used.
|
|
"""
|
|
return self.get_fields(user, timestamp)
|
|
|
|
|
|
class GalleryModelMixin(models.Model):
|
|
"""Mixin for models that has gallery."""
|
|
|
|
@property
|
|
def crop_gallery(self):
|
|
if hasattr(self, 'gallery'):
|
|
gallery = []
|
|
images = self.gallery.all()
|
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
|
if p.startswith(self._meta.model_name.lower())]
|
|
for image in images:
|
|
d = {
|
|
'id': image.id,
|
|
'title': image.title,
|
|
'original_url': image.image.url,
|
|
'orientation_display': image.get_orientation_display(),
|
|
'auto_crop_images': {},
|
|
}
|
|
for crop in crop_parameters:
|
|
d['auto_crop_images'].update({crop: image.get_image_url(crop)})
|
|
gallery.append(d)
|
|
return gallery
|
|
|
|
@property
|
|
def crop_main_image(self):
|
|
if hasattr(self, 'main_image') and self.main_image:
|
|
image = self.main_image
|
|
image_property = {
|
|
'id': image.id,
|
|
'title': image.title,
|
|
'original_url': image.image.url,
|
|
'orientation_display': image.get_orientation_display(),
|
|
'auto_crop_images': {},
|
|
}
|
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
|
if p.startswith(self._meta.model_name.lower())]
|
|
for crop in crop_parameters:
|
|
image_property['auto_crop_images'].update(
|
|
{crop: image.get_image_url(crop)}
|
|
)
|
|
return image_property
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
abstract = True
|
|
|
|
|
|
class IntermediateGalleryModelQuerySet(models.QuerySet):
|
|
"""Extended QuerySet."""
|
|
|
|
def main_image(self):
|
|
"""Return objects with flag is_main is True"""
|
|
return self.filter(is_main=True)
|
|
|
|
|
|
class IntermediateGalleryModelMixin(models.Model):
|
|
"""Mixin for intermediate gallery model."""
|
|
|
|
is_main = models.BooleanField(default=False,
|
|
verbose_name=_('Is the main image'))
|
|
|
|
objects = IntermediateGalleryModelQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
abstract = True
|
|
|
|
def __str__(self):
|
|
"""Overridden str method."""
|
|
if hasattr(self, 'image'):
|
|
return self.image.title if self.image.title else self.id
|
|
|
|
|
|
class HasTagsMixin(models.Model):
|
|
"""Mixin for filtering tags"""
|
|
|
|
@property
|
|
def visible_tags(self):
|
|
return self.tags.filter(category__public=True).prefetch_related('category')\
|
|
.exclude(category__value_type='bool')
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
abstract = True
|
|
|
|
|
|
class FavoritesMixin:
|
|
"""Append favorites_for_user property."""
|
|
|
|
@property
|
|
def favorites_for_users(self):
|
|
return self.favorites.aggregate(arr=ArrayAgg('user_id')).get('arr')
|
|
|
|
timezone.datetime.now().date().isoformat() |