Merge remote-tracking branch 'origin/develop' into nes_transfer
This commit is contained in:
commit
56b2b243da
|
|
@ -118,22 +118,23 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
|
||||||
instances = getattr(self, f'{related_object}')
|
instances = getattr(self, f'{related_object}')
|
||||||
if instances.exists():
|
if instances.exists():
|
||||||
for instance in instances.all():
|
for instance in instances.all():
|
||||||
raw_object = (instance.id, instance.slug) if hasattr(instance, 'slug') else (
|
raw_object = (instance.id, instance.establishment_type.index_name,
|
||||||
instance.id, None
|
instance.slug) if \
|
||||||
)
|
hasattr(instance, 'slug') else (instance.id, None, None)
|
||||||
raw_objects.append(raw_object)
|
raw_objects.append(raw_object)
|
||||||
|
|
||||||
# parse slugs
|
# parse slugs
|
||||||
related_objects = []
|
related_objects = []
|
||||||
object_names = set()
|
object_names = set()
|
||||||
re_pattern = r'[\w]+'
|
re_pattern = r'[\w]+'
|
||||||
for object_id, raw_name, in raw_objects:
|
for object_id, object_type, raw_name, in raw_objects:
|
||||||
result = re.findall(re_pattern, raw_name)
|
result = re.findall(re_pattern, raw_name)
|
||||||
if result:
|
if result:
|
||||||
name = ' '.join(result).capitalize()
|
name = ' '.join(result).capitalize()
|
||||||
if name not in object_names:
|
if name not in object_names:
|
||||||
related_objects.append({
|
related_objects.append({
|
||||||
'id': object_id,
|
'id': object_id,
|
||||||
|
'establishment_type': object_type,
|
||||||
'name': name
|
'name': name
|
||||||
})
|
})
|
||||||
object_names.add(name)
|
object_names.add(name)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import boto3
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from establishment.models import EstablishmentSubType
|
||||||
|
from gallery.models import Image
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """
|
||||||
|
Fill establishment type by index names.
|
||||||
|
Steps:
|
||||||
|
1 Upload default images into s3 bucket
|
||||||
|
2 Run command ./manage.py fill_artisan_default_image
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--template_image_folder_name',
|
||||||
|
help='Template image folder in Amazon S3 bucket'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
not_updated = 0
|
||||||
|
template_image_folder_name = kwargs.get('template_image_folder_name')
|
||||||
|
if (template_image_folder_name and
|
||||||
|
hasattr(settings, 'AWS_ACCESS_KEY_ID') and
|
||||||
|
hasattr(settings, 'AWS_SECRET_ACCESS_KEY') and
|
||||||
|
hasattr(settings, 'AWS_STORAGE_BUCKET_NAME')):
|
||||||
|
to_update = []
|
||||||
|
s3 = boto3.resource('s3')
|
||||||
|
s3_bucket = s3.Bucket(settings.AWS_STORAGE_BUCKET_NAME)
|
||||||
|
|
||||||
|
for object_summary in s3_bucket.objects.filter(Prefix=f'media/{template_image_folder_name}/'):
|
||||||
|
uri_path = object_summary.key
|
||||||
|
filename = uri_path.split('/')[-1:][0]
|
||||||
|
if filename:
|
||||||
|
artisan_index_slice = filename.split('.')[:-1][0] \
|
||||||
|
.split('_')[2:]
|
||||||
|
if len(artisan_index_slice) > 1:
|
||||||
|
artisan_index_name = '_'.join(artisan_index_slice)
|
||||||
|
else:
|
||||||
|
artisan_index_name = artisan_index_slice[0]
|
||||||
|
|
||||||
|
attachment_suffix_url = f'{template_image_folder_name}/{filename}'
|
||||||
|
|
||||||
|
# check artisan in db
|
||||||
|
artisan_qs = EstablishmentSubType.objects.filter(index_name__iexact=artisan_index_name,
|
||||||
|
establishment_type__index_name__iexact='artisan')
|
||||||
|
if artisan_qs.exists():
|
||||||
|
artisan = artisan_qs.first()
|
||||||
|
image, created = Image.objects.get_or_create(image=attachment_suffix_url,
|
||||||
|
defaults={
|
||||||
|
'image': attachment_suffix_url,
|
||||||
|
'orientation': Image.HORIZONTAL,
|
||||||
|
'title': f'{artisan.__str__()} '
|
||||||
|
f'{artisan.id} - '
|
||||||
|
f'{attachment_suffix_url}'})
|
||||||
|
if created:
|
||||||
|
# update artisan instance
|
||||||
|
artisan.default_image = image
|
||||||
|
to_update.append(artisan)
|
||||||
|
else:
|
||||||
|
not_updated += 1
|
||||||
|
|
||||||
|
EstablishmentSubType.objects.bulk_update(to_update, ['default_image', ])
|
||||||
|
self.stdout.write(self.style.WARNING(f'Updated {len(to_update)} objects.'))
|
||||||
|
self.stdout.write(self.style.WARNING(f'Not updated {not_updated} objects.'))
|
||||||
25
apps/establishment/migrations/0069_auto_20191220_1007.py
Normal file
25
apps/establishment/migrations/0069_auto_20191220_1007.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-20 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0007_auto_20191211_1528'),
|
||||||
|
('establishment', '0068_auto_20191220_0914'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='default_image',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishment_sub_types', to='gallery.Image', verbose_name='default image'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='default_image',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishment_types', to='gallery.Image', verbose_name='default image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -51,6 +51,10 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='establishment_types',
|
related_name='establishment_types',
|
||||||
verbose_name=_('Tag'))
|
verbose_name=_('Tag'))
|
||||||
|
default_image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
related_name='establishment_types',
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name='default image')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -85,6 +89,10 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='establishment_subtypes',
|
related_name='establishment_subtypes',
|
||||||
verbose_name=_('Tag'))
|
verbose_name=_('Tag'))
|
||||||
|
default_image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
related_name='establishment_sub_types',
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name='default image')
|
||||||
|
|
||||||
objects = EstablishmentSubTypeManager()
|
objects = EstablishmentSubTypeManager()
|
||||||
|
|
||||||
|
|
@ -105,7 +113,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
return self.select_related('address', 'establishment_type'). \
|
return self.select_related('address', 'establishment_type'). \
|
||||||
prefetch_related('tags')
|
prefetch_related('tags', 'tags__translation')
|
||||||
|
|
||||||
def with_schedule(self):
|
def with_schedule(self):
|
||||||
"""Return qs with related schedule."""
|
"""Return qs with related schedule."""
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,8 @@ class MenuRUDSerializers(ProjectModelSerializer):
|
||||||
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for EstablishmentType model."""
|
"""Serializer for EstablishmentType model."""
|
||||||
name_translated = TranslatedField()
|
name_translated = TranslatedField()
|
||||||
|
default_image_url = serializers.ImageField(source='default_image.image',
|
||||||
|
allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -107,6 +109,7 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'name_translated',
|
'name_translated',
|
||||||
'use_subtypes',
|
'use_subtypes',
|
||||||
'index_name',
|
'index_name',
|
||||||
|
'default_image_url',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'write_only': True},
|
'name': {'write_only': True},
|
||||||
|
|
@ -129,8 +132,9 @@ class EstablishmentTypeGeoSerializer(EstablishmentTypeBaseSerializer):
|
||||||
|
|
||||||
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for EstablishmentSubType models."""
|
"""Serializer for EstablishmentSubType models."""
|
||||||
|
|
||||||
name_translated = TranslatedField()
|
name_translated = TranslatedField()
|
||||||
|
default_image_url = serializers.ImageField(source='default_image.image',
|
||||||
|
allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -141,6 +145,7 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'name_translated',
|
'name_translated',
|
||||||
'establishment_type',
|
'establishment_type',
|
||||||
'index_name',
|
'index_name',
|
||||||
|
'default_image_url',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'write_only': True},
|
'name': {'write_only': True},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from sorl.thumbnail import delete
|
from sorl import thumbnail
|
||||||
from sorl.thumbnail.fields import ImageField as SORLImageField
|
from sorl.thumbnail.fields import ImageField as SORLImageField
|
||||||
|
|
||||||
from utils.methods import image_path
|
from utils.methods import image_path
|
||||||
|
|
@ -47,7 +47,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Delete from remote storage
|
# Delete from remote storage
|
||||||
delete(file_=self.image.file, delete_file=completely)
|
thumbnail.delete(file_=self.image.file, delete_file=completely)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
|
|
@ -481,6 +481,19 @@ class Panel(ProjectBaseMixin):
|
||||||
columns = [col[0] for col in cursor.description]
|
columns = [col[0] for col in cursor.description]
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
with connections['default'].cursor() as cursor:
|
||||||
|
try:
|
||||||
|
cursor.execute(self.query)
|
||||||
|
except Exception as er:
|
||||||
|
raise UnprocessableEntityError()
|
||||||
|
return self._raw_columns(cursor)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
with connections['default'].cursor() as cursor:
|
||||||
|
cursor.execute(self.query)
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
def _raw_page(self, raw, request):
|
def _raw_page(self, raw, request):
|
||||||
page = request.query_params.get('page', 0)
|
page = request.query_params.get('page', 0)
|
||||||
page_size = request.query_params.get('page_size', 0)
|
page_size = request.query_params.get('page_size', 0)
|
||||||
|
|
|
||||||
14
apps/main/tasks.py
Normal file
14
apps/main/tasks.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Task methods for main app."""
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
from account.models import User
|
||||||
|
from main.models import Panel
|
||||||
|
from utils.export import SendExport
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def send_export_to_email(panel_id, user_id, file_type='csv'):
|
||||||
|
panel = Panel.objects.get(id=panel_id)
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
SendExport(user, panel, file_type).send()
|
||||||
|
|
@ -24,8 +24,9 @@ urlpatterns = [
|
||||||
name='page-types-list-create'),
|
name='page-types-list-create'),
|
||||||
path('panels/', views.PanelsListCreateView.as_view(), name='panels'),
|
path('panels/', views.PanelsListCreateView.as_view(), name='panels'),
|
||||||
path('panels/<int:pk>/', views.PanelsRUDView.as_view(), name='panels-rud'),
|
path('panels/<int:pk>/', views.PanelsRUDView.as_view(), name='panels-rud'),
|
||||||
path('panels/<int:pk>/execute/', views.PanelsExecuteView.as_view(), name='panels-execute')
|
path('panels/<int:pk>/execute/', views.PanelsExecuteView.as_view(), name='panels-execute'),
|
||||||
|
path('panels/<int:pk>/csv/', views.PanelsExportCSVView.as_view(), name='panels-csv'),
|
||||||
|
path('panels/<int:pk>/xls/', views.PanelsExecuteXLSView.as_view(), name='panels-xls')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, status
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from main import serializers
|
from main import serializers
|
||||||
|
from main import tasks
|
||||||
from main.filters import AwardFilter
|
from main.filters import AwardFilter
|
||||||
from main.models import Award, Footer, PageType, Panel
|
from main.models import Award, Footer, PageType, Panel
|
||||||
from main.views import SiteSettingsView, SiteListView
|
from main.views import SiteSettingsView, SiteListView
|
||||||
|
|
@ -121,3 +123,35 @@ class PanelsExecuteView(generics.ListAPIView):
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
panel = get_object_or_404(Panel, id=self.kwargs['pk'])
|
panel = get_object_or_404(Panel, id=self.kwargs['pk'])
|
||||||
return Response(panel.execute_query(request))
|
return Response(panel.execute_query(request))
|
||||||
|
|
||||||
|
|
||||||
|
class PanelsExportCSVView(PanelsExecuteView):
|
||||||
|
"""Export panels via csv view."""
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
queryset = Panel.objects.all()
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
panel = get_object_or_404(Panel, id=self.kwargs['pk'])
|
||||||
|
# make task for celery
|
||||||
|
tasks.send_export_to_email.delay(
|
||||||
|
panel_id=panel.id, user_id=request.user.id)
|
||||||
|
return Response(
|
||||||
|
{"success": _('The file will be sent to your email.')},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PanelsExecuteXLSView(PanelsExecuteView):
|
||||||
|
"""Export panels via xlsx view."""
|
||||||
|
permission_classes = (permissions.IsAdminUser,)
|
||||||
|
queryset = Panel.objects.all()
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
panel = get_object_or_404(Panel, id=self.kwargs['pk'])
|
||||||
|
# make task for celery
|
||||||
|
tasks.send_export_to_email.delay(
|
||||||
|
panel_id=panel.id, user_id=request.user.id, file_type='xls')
|
||||||
|
return Response(
|
||||||
|
{"success": _('The file will be sent to your email.')},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class NewsQuerySet(TranslationQuerysetMixin):
|
||||||
|
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
return self.select_related('news_type', 'country').prefetch_related('tags')
|
return self.select_related('news_type', 'country').prefetch_related('tags', 'tags__translation')
|
||||||
|
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
"""Return qs with related objects."""
|
"""Return qs with related objects."""
|
||||||
|
|
|
||||||
25
apps/product/migrations/0022_auto_20191220_1007.py
Normal file
25
apps/product/migrations/0022_auto_20191220_1007.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-20 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gallery', '0007_auto_20191211_1528'),
|
||||||
|
('product', '0021_auto_20191212_0926'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='productsubtype',
|
||||||
|
name='default_image',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_sub_types', to='gallery.Image', verbose_name='default image'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='producttype',
|
||||||
|
name='default_image',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='product_types', to='gallery.Image', verbose_name='default image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -37,6 +37,10 @@ class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='product_types',
|
related_name='product_types',
|
||||||
verbose_name=_('Tag categories'))
|
verbose_name=_('Tag categories'))
|
||||||
|
default_image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
related_name='product_types',
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name='default image')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -62,6 +66,10 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
||||||
verbose_name=_('Index name'))
|
verbose_name=_('Index name'))
|
||||||
|
default_image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
||||||
|
related_name='product_sub_types',
|
||||||
|
blank=True, null=True, default=None,
|
||||||
|
verbose_name='default image')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -83,7 +91,7 @@ class ProductQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
return self.select_related('product_type', 'establishment') \
|
return self.select_related('product_type', 'establishment') \
|
||||||
.prefetch_related('product_type__subtypes')
|
.prefetch_related('product_type__subtypes', 'tags', 'tags__translation')
|
||||||
|
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
"""Returns qs with almost all related objects."""
|
"""Returns qs with almost all related objects."""
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
name_translated = TranslatedField()
|
name_translated = TranslatedField()
|
||||||
index_name_display = serializers.CharField(source='get_index_name_display',
|
index_name_display = serializers.CharField(source='get_index_name_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
default_image_url = serializers.ImageField(source='default_image.image',
|
||||||
|
allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ProductSubType
|
model = models.ProductSubType
|
||||||
|
|
@ -41,12 +43,15 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'id',
|
'id',
|
||||||
'name_translated',
|
'name_translated',
|
||||||
'index_name_display',
|
'index_name_display',
|
||||||
|
'default_image_url',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
"""ProductType base serializer"""
|
"""ProductType base serializer"""
|
||||||
name_translated = TranslatedField()
|
name_translated = TranslatedField()
|
||||||
|
default_image_url = serializers.ImageField(source='default_image.image',
|
||||||
|
allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ProductType
|
model = models.ProductType
|
||||||
|
|
@ -54,6 +59,7 @@ class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
||||||
'id',
|
'id',
|
||||||
'name_translated',
|
'name_translated',
|
||||||
'index_name',
|
'index_name',
|
||||||
|
'default_image_url',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,8 @@ class EstablishmentDocument(Document):
|
||||||
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
||||||
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
||||||
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
||||||
|
'closed_at_indexing': fields.DateField(),
|
||||||
|
'opening_at_indexing': fields.DateField(),
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
address = fields.ObjectField(
|
address = fields.ObjectField(
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class TagCategoryFilterSet(TagsBaseFilterSet):
|
||||||
# todo: filter by establishment type
|
# todo: filter by establishment type
|
||||||
def by_establishment_type(self, queryset, name, value):
|
def by_establishment_type(self, queryset, name, value):
|
||||||
if value == EstablishmentType.ARTISAN:
|
if value == EstablishmentType.ARTISAN:
|
||||||
qs = models.TagCategory.objects.filter(index_name='shop_category')
|
qs = models.TagCategory.objects.with_base_related().filter(index_name='shop_category')
|
||||||
else:
|
else:
|
||||||
qs = queryset.by_establishment_type(value)
|
qs = queryset.by_establishment_type(value)
|
||||||
return qs
|
return qs
|
||||||
|
|
@ -73,10 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
|
||||||
|
|
||||||
def by_establishment_type(self, queryset, name, value):
|
def by_establishment_type(self, queryset, name, value):
|
||||||
if value == EstablishmentType.ARTISAN:
|
if value == EstablishmentType.ARTISAN:
|
||||||
qs = models.Tag.objects.by_category_index_name('shop_category')
|
qs = models.Tag.objects.filter(index_name__in=settings.ARTISANS_CHOSEN_TAGS)
|
||||||
if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES:
|
if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES:
|
||||||
qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id')
|
qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id')
|
||||||
return qs.exclude(establishments__isnull=True)[0:8]
|
return qs.exclude(establishments__isnull=True)
|
||||||
return queryset.by_establishment_type(value)
|
return queryset.by_establishment_type(value)
|
||||||
|
|
||||||
# TMP TODO remove it later
|
# TMP TODO remove it later
|
||||||
|
|
|
||||||
46
apps/tag/migrations/0016_auto_20191220_1224.py
Normal file
46
apps/tag/migrations/0016_auto_20191220_1224.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-20 12:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def fill_translations(apps, schemaeditor):
|
||||||
|
Tag = apps.get_model('tag', 'Tag')
|
||||||
|
TagCategory = apps.get_model('tag', 'TagCategory')
|
||||||
|
SiteInterfaceDictionary = apps.get_model('translation', 'SiteInterfaceDictionary')
|
||||||
|
|
||||||
|
for tag_category in TagCategory.objects.all():
|
||||||
|
if tag_category.label:
|
||||||
|
t = SiteInterfaceDictionary(text=tag_category.label)
|
||||||
|
t.save()
|
||||||
|
tag_category.translation = t
|
||||||
|
tag_category.save()
|
||||||
|
|
||||||
|
for tag in Tag.objects.all():
|
||||||
|
if tag.label:
|
||||||
|
t = SiteInterfaceDictionary(text=tag.label)
|
||||||
|
t.save()
|
||||||
|
tag.translation = t
|
||||||
|
tag.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0007_language_is_active'),
|
||||||
|
('tag', '0015_auto_20191118_1210'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='translation',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag', to='translation.SiteInterfaceDictionary', verbose_name='Translation'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tagcategory',
|
||||||
|
name='translation',
|
||||||
|
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag_category', to='translation.SiteInterfaceDictionary', verbose_name='Translation'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_translations, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
21
apps/tag/migrations/0017_auto_20191220_1623.py
Normal file
21
apps/tag/migrations/0017_auto_20191220_1623.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 2.2.7 on 2019-12-20 16:23
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0016_auto_20191220_1224'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='label',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tagcategory',
|
||||||
|
name='label',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from configuration.models import TranslationSettings
|
from configuration.models import TranslationSettings
|
||||||
from location.models import Country
|
from location.models import Country
|
||||||
from utils.models import TJSONField, TranslatedFieldsMixin
|
from utils.models import IndexJSON
|
||||||
|
|
||||||
|
|
||||||
class TagQuerySet(models.QuerySet):
|
class TagQuerySet(models.QuerySet):
|
||||||
|
|
@ -29,12 +29,9 @@ class TagQuerySet(models.QuerySet):
|
||||||
return self.filter(category__establishment_types__index_name=index_name)
|
return self.filter(category__establishment_types__index_name=index_name)
|
||||||
|
|
||||||
|
|
||||||
class Tag(TranslatedFieldsMixin, models.Model):
|
class Tag(models.Model):
|
||||||
"""Tag model."""
|
"""Tag model."""
|
||||||
|
|
||||||
label = TJSONField(blank=True, null=True, default=None,
|
|
||||||
verbose_name=_('label'),
|
|
||||||
help_text='{"en-GB":"some text"}')
|
|
||||||
value = models.CharField(_('indexing name'), max_length=255, blank=True, db_index=True,
|
value = models.CharField(_('indexing name'), max_length=255, blank=True, db_index=True,
|
||||||
null=True, default=None)
|
null=True, default=None)
|
||||||
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
|
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
|
||||||
|
|
@ -48,6 +45,16 @@ class Tag(TranslatedFieldsMixin, models.Model):
|
||||||
|
|
||||||
old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'),
|
old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'),
|
||||||
blank=True, null=True, default=None)
|
blank=True, null=True, default=None)
|
||||||
|
translation = models.ForeignKey('translation.SiteInterfaceDictionary', on_delete=models.SET_NULL,
|
||||||
|
null=True, related_name='tag', verbose_name=_('Translation'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label_indexing(self):
|
||||||
|
base_dict = self.translation.text if self.translation and isinstance(self.translation.text, dict) else {}
|
||||||
|
index = IndexJSON()
|
||||||
|
for k, v in base_dict.items():
|
||||||
|
setattr(index, k, v)
|
||||||
|
return index
|
||||||
|
|
||||||
objects = TagQuerySet.as_manager()
|
objects = TagQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -88,7 +95,7 @@ class TagCategoryQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def with_base_related(self):
|
def with_base_related(self):
|
||||||
"""Select related objects."""
|
"""Select related objects."""
|
||||||
return self.prefetch_related('tags')
|
return self.prefetch_related('tags', 'tags__translation').select_related('translation')
|
||||||
|
|
||||||
def with_extended_related(self):
|
def with_extended_related(self):
|
||||||
"""Select related objects."""
|
"""Select related objects."""
|
||||||
|
|
@ -119,7 +126,7 @@ class TagCategoryQuerySet(models.QuerySet):
|
||||||
return self.exclude(tags__isnull=switcher)
|
return self.exclude(tags__isnull=switcher)
|
||||||
|
|
||||||
|
|
||||||
class TagCategory(TranslatedFieldsMixin, models.Model):
|
class TagCategory(models.Model):
|
||||||
"""Tag base category model."""
|
"""Tag base category model."""
|
||||||
|
|
||||||
STRING = 'string'
|
STRING = 'string'
|
||||||
|
|
@ -137,10 +144,6 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
|
||||||
(PERCENTAGE, _('percentage')),
|
(PERCENTAGE, _('percentage')),
|
||||||
(BOOLEAN, _('boolean')),
|
(BOOLEAN, _('boolean')),
|
||||||
)
|
)
|
||||||
|
|
||||||
label = TJSONField(blank=True, null=True, default=None,
|
|
||||||
verbose_name=_('label'),
|
|
||||||
help_text='{"en-GB":"some text"}')
|
|
||||||
country = models.ForeignKey('location.Country',
|
country = models.ForeignKey('location.Country',
|
||||||
on_delete=models.SET_NULL, null=True,
|
on_delete=models.SET_NULL, null=True,
|
||||||
default=None)
|
default=None)
|
||||||
|
|
@ -151,6 +154,16 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
|
||||||
value_type = models.CharField(_('value type'), max_length=255,
|
value_type = models.CharField(_('value type'), max_length=255,
|
||||||
choices=VALUE_TYPE_CHOICES, default=LIST, )
|
choices=VALUE_TYPE_CHOICES, default=LIST, )
|
||||||
old_id = models.IntegerField(blank=True, null=True)
|
old_id = models.IntegerField(blank=True, null=True)
|
||||||
|
translation = models.OneToOneField('translation.SiteInterfaceDictionary', on_delete=models.SET_NULL,
|
||||||
|
null=True, related_name='tag_category', verbose_name=_('Translation'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label_indexing(self):
|
||||||
|
base_dict = self.translation.text if self.translation and isinstance(self.translation.text, dict) else {}
|
||||||
|
index = IndexJSON()
|
||||||
|
for k, v in base_dict.items():
|
||||||
|
setattr(index, k, v)
|
||||||
|
return index
|
||||||
|
|
||||||
objects = TagCategoryQuerySet.as_manager()
|
objects = TagCategoryQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,25 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment, EstablishmentType
|
||||||
from establishment.models import EstablishmentType
|
|
||||||
from news.models import News
|
from news.models import News
|
||||||
from news.models import NewsType
|
from news.models import NewsType
|
||||||
from tag import models
|
from tag import models
|
||||||
from utils.exceptions import BindingObjectNotFound
|
from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound
|
||||||
from utils.exceptions import ObjectAlreadyAdded
|
|
||||||
from utils.exceptions import RemovedBindingObjectNotFound
|
|
||||||
from utils.serializers import TranslatedField
|
from utils.serializers import TranslatedField
|
||||||
|
from utils.models import get_default_locale, get_language, to_locale
|
||||||
|
|
||||||
|
|
||||||
|
def translate_obj(obj):
|
||||||
|
if not obj.translation or not isinstance(obj.translation.text, dict):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
field = obj.translation.text
|
||||||
|
return field.get(to_locale(get_language()),
|
||||||
|
field.get(get_default_locale(),
|
||||||
|
next(iter(field.values()))))
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TagBaseSerializer(serializers.ModelSerializer):
|
class TagBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -19,8 +29,11 @@ class TagBaseSerializer(serializers.ModelSerializer):
|
||||||
def get_extra_kwargs(self):
|
def get_extra_kwargs(self):
|
||||||
return super().get_extra_kwargs()
|
return super().get_extra_kwargs()
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
|
||||||
index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
|
index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
|
||||||
|
label_translated = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
|
def get_label_translated(self, obj):
|
||||||
|
return translate_obj(obj)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -47,8 +60,10 @@ class TagBackOfficeSerializer(TagBaseSerializer):
|
||||||
|
|
||||||
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||||
"""SHORT Serializer for TagCategory"""
|
"""SHORT Serializer for TagCategory"""
|
||||||
|
label_translated = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
def get_label_translated(self, obj):
|
||||||
|
return translate_obj(obj)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -56,7 +71,6 @@ class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||||
model = models.TagCategory
|
model = models.TagCategory
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'label_translated',
|
|
||||||
'index_name',
|
'index_name',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,8 +78,8 @@ class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||||
class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
tags = TagBaseSerializer(many=True, allow_null=True)
|
||||||
tags = SerializerMethodField()
|
label_translated = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -78,33 +92,17 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
'tags',
|
'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_tags(self, obj):
|
def get_label_translated(self, obj):
|
||||||
query_params = dict(self.context['request'].query_params)
|
return translate_obj(obj)
|
||||||
|
|
||||||
if len(query_params) > 1:
|
|
||||||
return []
|
|
||||||
|
|
||||||
params = {}
|
|
||||||
if 'establishment_type' in query_params:
|
|
||||||
params = {
|
|
||||||
'establishments__isnull': False,
|
|
||||||
}
|
|
||||||
elif 'product_type' in query_params:
|
|
||||||
params = {
|
|
||||||
'products__isnull': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = obj.tags.filter(**params).distinct()
|
|
||||||
return TagBaseSerializer(instance=tags, many=True, read_only=True).data
|
|
||||||
|
|
||||||
|
|
||||||
class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
|
||||||
filters = SerializerMethodField()
|
filters = SerializerMethodField()
|
||||||
param_name = SerializerMethodField()
|
param_name = SerializerMethodField()
|
||||||
type = SerializerMethodField()
|
type = SerializerMethodField()
|
||||||
|
label_translated = serializers.SerializerMethodField(allow_null=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -127,6 +125,9 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
return 'wine_colors_id__in'
|
return 'wine_colors_id__in'
|
||||||
return 'tags_id__in'
|
return 'tags_id__in'
|
||||||
|
|
||||||
|
def get_label_translated(self, obj):
|
||||||
|
return translate_obj(obj)
|
||||||
|
|
||||||
def get_fields(self, *args, **kwargs):
|
def get_fields(self, *args, **kwargs):
|
||||||
fields = super(FiltersTagCategoryBaseSerializer, self).get_fields()
|
fields = super(FiltersTagCategoryBaseSerializer, self).get_fields()
|
||||||
|
|
||||||
|
|
@ -157,10 +158,13 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model TagCategory."""
|
"""Serializer for model TagCategory."""
|
||||||
|
|
||||||
label_translated = TranslatedField()
|
label_translated = serializers.SerializerMethodField(allow_null=True, read_only=True)
|
||||||
value_type_display = serializers.CharField(source='get_value_type_display',
|
value_type_display = serializers.CharField(source='get_value_type_display',
|
||||||
read_only=True)
|
read_only=True)
|
||||||
|
|
||||||
|
def get_label_translated(self, obj):
|
||||||
|
return translate_obj(obj)
|
||||||
|
|
||||||
class Meta(TagCategoryBaseSerializer.Meta):
|
class Meta(TagCategoryBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
fields = [
|
fields = [
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from datetime import time
|
from datetime import time, datetime
|
||||||
|
|
||||||
from utils.models import ProjectBaseMixin
|
from utils.models import ProjectBaseMixin
|
||||||
|
|
||||||
|
|
@ -59,6 +59,14 @@ class Timetable(ProjectBaseMixin):
|
||||||
def opening_at_str(self):
|
def opening_at_str(self):
|
||||||
return str(self.opening_at) if self.opening_at else None
|
return str(self.opening_at) if self.opening_at else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed_at_indexing(self):
|
||||||
|
return datetime.combine(time=self.closed_at, date=datetime(1970, 1, 1 + self.weekday).date()) if self.closed_at else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opening_at_indexing(self):
|
||||||
|
return datetime.combine(time=self.opening_at, date=datetime(1970, 1, 1 + self.weekday).date()) if self.opening_at else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def opening_time(self):
|
def opening_time(self):
|
||||||
return self.opening_at or self.lunch_start or self.dinner_start
|
return self.opening_at or self.lunch_start or self.dinner_start
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from django.utils.text import slugify
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from tag.models import Tag
|
from tag.models import Tag
|
||||||
|
from translation.models import SiteInterfaceDictionary
|
||||||
from transfer.mixins import TransferSerializerMixin
|
from transfer.mixins import TransferSerializerMixin
|
||||||
from transfer.models import Cepages
|
from transfer.models import Cepages
|
||||||
|
|
||||||
|
|
@ -36,8 +37,11 @@ class AssemblageTagSerializer(TransferSerializerMixin):
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
qs = self.Meta.model.objects.filter(**validated_data)
|
qs = self.Meta.model.objects.filter(**validated_data)
|
||||||
category = validated_data.get('category')
|
category = validated_data.get('category')
|
||||||
|
translations = validated_data.pop('label')
|
||||||
if not qs.exists() and category:
|
if not qs.exists() and category:
|
||||||
return super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
|
SiteInterfaceDictionary.objects.update_or_create_for_tag(instance, translations)
|
||||||
|
return instance
|
||||||
|
|
||||||
def get_tag_value(self, cepage, percent):
|
def get_tag_value(self, cepage, percent):
|
||||||
if cepage and percent:
|
if cepage and percent:
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.apps import apps
|
||||||
from utils.models import ProjectBaseMixin, LocaleManagerMixin
|
from utils.models import ProjectBaseMixin, LocaleManagerMixin
|
||||||
|
|
||||||
|
|
||||||
class LanguageQuerySet(models.QuerySet):
|
class LanguageQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model Language"""
|
"""QuerySet for model Language"""
|
||||||
|
|
||||||
|
|
@ -50,6 +50,44 @@ class Language(models.Model):
|
||||||
class SiteInterfaceDictionaryManager(LocaleManagerMixin):
|
class SiteInterfaceDictionaryManager(LocaleManagerMixin):
|
||||||
"""Extended manager for SiteInterfaceDictionary model."""
|
"""Extended manager for SiteInterfaceDictionary model."""
|
||||||
|
|
||||||
|
def update_or_create_for_tag(self, tag, translations: dict):
|
||||||
|
Tag = apps.get_model('tag', 'Tag')
|
||||||
|
"""Creates or updates translation for EXISTING in DB Tag"""
|
||||||
|
if not tag.pk or not isinstance(tag, Tag):
|
||||||
|
raise NotImplementedError
|
||||||
|
if tag.translation:
|
||||||
|
tag.translation.text = translations
|
||||||
|
tag.translation.page = 'tag'
|
||||||
|
tag.translation.keywords = f'tag-{tag.pk}'
|
||||||
|
else:
|
||||||
|
trans = SiteInterfaceDictionary({
|
||||||
|
'text': translations,
|
||||||
|
'page': 'tag',
|
||||||
|
'keywords': f'tag-{tag.pk}'
|
||||||
|
})
|
||||||
|
trans.save()
|
||||||
|
tag.translation = trans
|
||||||
|
tag.save()
|
||||||
|
|
||||||
|
def update_or_create_for_tag_category(self, tag_category, translations: dict):
|
||||||
|
"""Creates or updates translation for EXISTING in DB TagCategory"""
|
||||||
|
TagCategory = apps.get_model('tag', 'TagCategory')
|
||||||
|
if not tag_category.pk or not isinstance(tag_category, TagCategory):
|
||||||
|
raise NotImplementedError
|
||||||
|
if tag_category.translation:
|
||||||
|
tag_category.translation.text = translations
|
||||||
|
tag_category.translation.page = 'tag'
|
||||||
|
tag_category.translation.keywords = f'tag_category-{tag_category.pk}'
|
||||||
|
else:
|
||||||
|
trans = SiteInterfaceDictionary({
|
||||||
|
'text': translations,
|
||||||
|
'page': 'tag',
|
||||||
|
'keywords': f'tag_category-{tag_category.pk}'
|
||||||
|
})
|
||||||
|
trans.save()
|
||||||
|
tag_category.translation = trans
|
||||||
|
tag_category.save()
|
||||||
|
|
||||||
|
|
||||||
class SiteInterfaceDictionary(ProjectBaseMixin):
|
class SiteInterfaceDictionary(ProjectBaseMixin):
|
||||||
"""Site interface dictionary model."""
|
"""Site interface dictionary model."""
|
||||||
|
|
|
||||||
115
apps/utils/export.py
Normal file
115
apps/utils/export.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import csv
|
||||||
|
import xlsxwriter
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from smtplib import SMTPException
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
|
||||||
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SendExport:
|
||||||
|
|
||||||
|
def __init__(self, user, panel, file_type='csv'):
|
||||||
|
self.type_mapper = {
|
||||||
|
"csv": self.make_csv_file,
|
||||||
|
"xls": self.make_xls_file
|
||||||
|
}
|
||||||
|
self.file_type = file_type
|
||||||
|
self.user = user
|
||||||
|
self.panel = panel
|
||||||
|
self.email_from = settings.EMAIL_HOST_USER
|
||||||
|
self.email_subject = f'Export panel: {self.get_file_name()}'
|
||||||
|
self.email_body = 'Exported panel data'
|
||||||
|
self.get_file_method = self.type_mapper[file_type]
|
||||||
|
self.file_path = os.path.join(
|
||||||
|
settings.STATIC_ROOT,
|
||||||
|
'email', tempfile.gettempdir(),
|
||||||
|
self.get_file_name()
|
||||||
|
)
|
||||||
|
self.success = False
|
||||||
|
|
||||||
|
def get_file_name(self):
|
||||||
|
name = '_'.join(self.panel.name.split(' '))
|
||||||
|
return f'export_{name.lower()}.{self.file_type}'
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self.panel.get_data()
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
try:
|
||||||
|
header = self.panel.get_headers()
|
||||||
|
self.success = True
|
||||||
|
return header
|
||||||
|
except Exception as err:
|
||||||
|
logger.info(f'HEADER:{err}')
|
||||||
|
|
||||||
|
def make_csv_file(self):
|
||||||
|
file_header = self.get_headers()
|
||||||
|
if not self.success:
|
||||||
|
return
|
||||||
|
with open(self.file_path, 'w') as f:
|
||||||
|
file_writer = csv.writer(f, quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
||||||
|
# Write headers to CSV file
|
||||||
|
file_writer.writerow(file_header)
|
||||||
|
for row in self.get_data():
|
||||||
|
file_writer.writerow(row)
|
||||||
|
|
||||||
|
def make_xls_file(self):
|
||||||
|
headings = self.get_headers()
|
||||||
|
if not self.success:
|
||||||
|
return
|
||||||
|
with xlsxwriter.Workbook(self.file_path) as workbook:
|
||||||
|
worksheet = workbook.add_worksheet()
|
||||||
|
|
||||||
|
# Add a bold format to use to highlight cells.
|
||||||
|
bold = workbook.add_format({'bold': True})
|
||||||
|
|
||||||
|
# Add the worksheet data that the charts will refer to.
|
||||||
|
data = self.get_data()
|
||||||
|
|
||||||
|
worksheet.write_row('A1', headings, bold)
|
||||||
|
for n, row in enumerate(data):
|
||||||
|
worksheet.write_row(f'A{n+2}', [str(i) for i in row])
|
||||||
|
workbook.close()
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
self.get_file_method()
|
||||||
|
print(f'ok: {self.file_path}')
|
||||||
|
self.send_email()
|
||||||
|
|
||||||
|
def get_file(self):
|
||||||
|
if os.path.exists(self.file_path) and os.path.isfile(self.file_path):
|
||||||
|
with open(self.file_path, 'rb') as export_file:
|
||||||
|
return export_file
|
||||||
|
else:
|
||||||
|
logger.info('COMMUTATOR:image file not found dir: {path}')
|
||||||
|
|
||||||
|
def send_email(self):
|
||||||
|
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
subject=self.email_subject,
|
||||||
|
body=self.email_body,
|
||||||
|
from_email=self.email_from,
|
||||||
|
to=[
|
||||||
|
self.user.email,
|
||||||
|
'kuzmenko.da@gmail.com',
|
||||||
|
'sinapsit@yandex.ru'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an inline attachment
|
||||||
|
if self.file_path and self.success:
|
||||||
|
msg.attach_file(self.file_path)
|
||||||
|
else:
|
||||||
|
msg.body = 'An error occurred while executing the request.'
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg.send()
|
||||||
|
logger.debug(f"COMMUTATOR:Email successfully sent")
|
||||||
|
except SMTPException as e:
|
||||||
|
logger.error(f"COMMUTATOR:Email connector: {e}")
|
||||||
|
|
@ -88,7 +88,6 @@ def translate_field(self, field_name, toggle_field_name=None):
|
||||||
return None
|
return None
|
||||||
return translate
|
return translate
|
||||||
|
|
||||||
|
|
||||||
# todo: refactor this
|
# todo: refactor this
|
||||||
class IndexJSON:
|
class IndexJSON:
|
||||||
|
|
||||||
|
|
@ -443,7 +442,8 @@ class HasTagsMixin(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visible_tags(self):
|
def visible_tags(self):
|
||||||
return self.tags.filter(category__public=True).prefetch_related('category')\
|
return self.tags.filter(category__public=True).prefetch_related('category',
|
||||||
|
'translation', 'category__translation')\
|
||||||
.exclude(category__value_type='bool')
|
.exclude(category__value_type='bool')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -516,8 +516,12 @@ PHONENUMBER_DEFAULT_REGION = "FR"
|
||||||
|
|
||||||
FALLBACK_LOCALE = 'en-GB'
|
FALLBACK_LOCALE = 'en-GB'
|
||||||
|
|
||||||
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
|
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
|
||||||
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
|
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']
|
||||||
|
ARTISANS_CHOSEN_TAGS = ['butchery', 'bakery', 'patisserie', 'cheese_shop', 'fish_shop', 'ice-cream_maker',
|
||||||
|
'wine_merchant', 'coffe_shop']
|
||||||
|
RECIPES_CHOSEN_TAGS = ['cook', 'eat', 'drink']
|
||||||
|
|
||||||
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
|
INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next']
|
||||||
|
|
||||||
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
|
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,11 @@ LOGGING = {
|
||||||
'py.warnings': {
|
'py.warnings': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
},
|
},
|
||||||
# 'django.db.backends': {
|
'django.db.backends': {
|
||||||
# 'handlers': ['console', ],
|
'handlers': ['console', ],
|
||||||
# 'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
# 'propagate': False,
|
'propagate': False,
|
||||||
# },
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,3 +63,6 @@ pycountry==19.8.18
|
||||||
|
|
||||||
# sql-tree
|
# sql-tree
|
||||||
django-mptt==0.9.1
|
django-mptt==0.9.1
|
||||||
|
|
||||||
|
# Export to Excel
|
||||||
|
XlsxWriter==1.2.6
|
||||||
Loading…
Reference in New Issue
Block a user