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}')
|
||||
if instances.exists():
|
||||
for instance in instances.all():
|
||||
raw_object = (instance.id, instance.slug) if hasattr(instance, 'slug') else (
|
||||
instance.id, None
|
||||
)
|
||||
raw_object = (instance.id, instance.establishment_type.index_name,
|
||||
instance.slug) if \
|
||||
hasattr(instance, 'slug') else (instance.id, None, None)
|
||||
raw_objects.append(raw_object)
|
||||
|
||||
# parse slugs
|
||||
related_objects = []
|
||||
object_names = set()
|
||||
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)
|
||||
if result:
|
||||
name = ' '.join(result).capitalize()
|
||||
if name not in object_names:
|
||||
related_objects.append({
|
||||
'id': object_id,
|
||||
'establishment_type': object_type,
|
||||
'name': 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',
|
||||
related_name='establishment_types',
|
||||
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:
|
||||
"""Meta class."""
|
||||
|
|
@ -85,6 +89,10 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
|||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||
related_name='establishment_subtypes',
|
||||
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()
|
||||
|
||||
|
|
@ -105,7 +113,7 @@ class EstablishmentQuerySet(models.QuerySet):
|
|||
def with_base_related(self):
|
||||
"""Return qs with related objects."""
|
||||
return self.select_related('address', 'establishment_type'). \
|
||||
prefetch_related('tags')
|
||||
prefetch_related('tags', 'tags__translation')
|
||||
|
||||
def with_schedule(self):
|
||||
"""Return qs with related schedule."""
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ class MenuRUDSerializers(ProjectModelSerializer):
|
|||
class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentType model."""
|
||||
name_translated = TranslatedField()
|
||||
default_image_url = serializers.ImageField(source='default_image.image',
|
||||
allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -107,6 +109,7 @@ class EstablishmentTypeBaseSerializer(serializers.ModelSerializer):
|
|||
'name_translated',
|
||||
'use_subtypes',
|
||||
'index_name',
|
||||
'default_image_url',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'write_only': True},
|
||||
|
|
@ -129,8 +132,9 @@ class EstablishmentTypeGeoSerializer(EstablishmentTypeBaseSerializer):
|
|||
|
||||
class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for EstablishmentSubType models."""
|
||||
|
||||
name_translated = TranslatedField()
|
||||
default_image_url = serializers.ImageField(source='default_image.image',
|
||||
allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -141,6 +145,7 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
|
|||
'name_translated',
|
||||
'establishment_type',
|
||||
'index_name',
|
||||
'default_image_url',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'write_only': True},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.db import models
|
||||
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 utils.methods import image_path
|
||||
|
|
@ -47,7 +47,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
|
|||
"""
|
||||
try:
|
||||
# Delete from remote storage
|
||||
delete(file_=self.image.file, delete_file=completely)
|
||||
thumbnail.delete(file_=self.image.file, delete_file=completely)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -481,6 +481,19 @@ class Panel(ProjectBaseMixin):
|
|||
columns = [col[0] for col in cursor.description]
|
||||
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):
|
||||
page = request.query_params.get('page', 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'),
|
||||
path('panels/', views.PanelsListCreateView.as_view(), name='panels'),
|
||||
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.utils.translation import gettext_lazy as _
|
||||
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.response import Response
|
||||
|
||||
from main import serializers
|
||||
from main import tasks
|
||||
from main.filters import AwardFilter
|
||||
from main.models import Award, Footer, PageType, Panel
|
||||
from main.views import SiteSettingsView, SiteListView
|
||||
|
|
@ -121,3 +123,35 @@ class PanelsExecuteView(generics.ListAPIView):
|
|||
def list(self, request, *args, **kwargs):
|
||||
panel = get_object_or_404(Panel, id=self.kwargs['pk'])
|
||||
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):
|
||||
"""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):
|
||||
"""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',
|
||||
related_name='product_types',
|
||||
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:
|
||||
"""Meta class."""
|
||||
|
|
@ -62,6 +66,10 @@ class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
|
|||
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
|
||||
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
||||
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:
|
||||
"""Meta class."""
|
||||
|
|
@ -83,7 +91,7 @@ class ProductQuerySet(models.QuerySet):
|
|||
|
||||
def with_base_related(self):
|
||||
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):
|
||||
"""Returns qs with almost all related objects."""
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
|||
name_translated = TranslatedField()
|
||||
index_name_display = serializers.CharField(source='get_index_name_display',
|
||||
read_only=True)
|
||||
default_image_url = serializers.ImageField(source='default_image.image',
|
||||
allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = models.ProductSubType
|
||||
|
|
@ -41,12 +43,15 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer):
|
|||
'id',
|
||||
'name_translated',
|
||||
'index_name_display',
|
||||
'default_image_url',
|
||||
]
|
||||
|
||||
|
||||
class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
||||
"""ProductType base serializer"""
|
||||
name_translated = TranslatedField()
|
||||
default_image_url = serializers.ImageField(source='default_image.image',
|
||||
allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = models.ProductType
|
||||
|
|
@ -54,6 +59,7 @@ class ProductTypeBaseSerializer(serializers.ModelSerializer):
|
|||
'id',
|
||||
'name_translated',
|
||||
'index_name',
|
||||
'default_image_url',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ class EstablishmentDocument(Document):
|
|||
'weekday_display': fields.KeywordField(attr='get_weekday_display'),
|
||||
'closed_at': fields.KeywordField(attr='closed_at_str'),
|
||||
'opening_at': fields.KeywordField(attr='opening_at_str'),
|
||||
'closed_at_indexing': fields.DateField(),
|
||||
'opening_at_indexing': fields.DateField(),
|
||||
}
|
||||
))
|
||||
address = fields.ObjectField(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class TagCategoryFilterSet(TagsBaseFilterSet):
|
|||
# todo: filter by establishment type
|
||||
def by_establishment_type(self, queryset, name, value):
|
||||
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:
|
||||
qs = queryset.by_establishment_type(value)
|
||||
return qs
|
||||
|
|
@ -73,10 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet):
|
|||
|
||||
def by_establishment_type(self, queryset, name, value):
|
||||
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:
|
||||
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)
|
||||
|
||||
# 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 location.models import Country
|
||||
from utils.models import TJSONField, TranslatedFieldsMixin
|
||||
from utils.models import IndexJSON
|
||||
|
||||
|
||||
class TagQuerySet(models.QuerySet):
|
||||
|
|
@ -29,12 +29,9 @@ class TagQuerySet(models.QuerySet):
|
|||
return self.filter(category__establishment_types__index_name=index_name)
|
||||
|
||||
|
||||
class Tag(TranslatedFieldsMixin, models.Model):
|
||||
class Tag(models.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,
|
||||
null=True, default=None)
|
||||
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'),
|
||||
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()
|
||||
|
||||
|
|
@ -88,7 +95,7 @@ class TagCategoryQuerySet(models.QuerySet):
|
|||
|
||||
def with_base_related(self):
|
||||
"""Select related objects."""
|
||||
return self.prefetch_related('tags')
|
||||
return self.prefetch_related('tags', 'tags__translation').select_related('translation')
|
||||
|
||||
def with_extended_related(self):
|
||||
"""Select related objects."""
|
||||
|
|
@ -119,7 +126,7 @@ class TagCategoryQuerySet(models.QuerySet):
|
|||
return self.exclude(tags__isnull=switcher)
|
||||
|
||||
|
||||
class TagCategory(TranslatedFieldsMixin, models.Model):
|
||||
class TagCategory(models.Model):
|
||||
"""Tag base category model."""
|
||||
|
||||
STRING = 'string'
|
||||
|
|
@ -137,10 +144,6 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
|
|||
(PERCENTAGE, _('percentage')),
|
||||
(BOOLEAN, _('boolean')),
|
||||
)
|
||||
|
||||
label = TJSONField(blank=True, null=True, default=None,
|
||||
verbose_name=_('label'),
|
||||
help_text='{"en-GB":"some text"}')
|
||||
country = models.ForeignKey('location.Country',
|
||||
on_delete=models.SET_NULL, null=True,
|
||||
default=None)
|
||||
|
|
@ -151,6 +154,16 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
|
|||
value_type = models.CharField(_('value type'), max_length=255,
|
||||
choices=VALUE_TYPE_CHOICES, default=LIST, )
|
||||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,25 @@
|
|||
from rest_framework import serializers
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
|
||||
from establishment.models import Establishment
|
||||
from establishment.models import EstablishmentType
|
||||
from establishment.models import Establishment, EstablishmentType
|
||||
from news.models import News
|
||||
from news.models import NewsType
|
||||
from tag import models
|
||||
from utils.exceptions import BindingObjectNotFound
|
||||
from utils.exceptions import ObjectAlreadyAdded
|
||||
from utils.exceptions import RemovedBindingObjectNotFound
|
||||
from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound
|
||||
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):
|
||||
|
|
@ -19,8 +29,11 @@ class TagBaseSerializer(serializers.ModelSerializer):
|
|||
def get_extra_kwargs(self):
|
||||
return super().get_extra_kwargs()
|
||||
|
||||
label_translated = TranslatedField()
|
||||
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:
|
||||
"""Meta class."""
|
||||
|
|
@ -47,8 +60,10 @@ class TagBackOfficeSerializer(TagBaseSerializer):
|
|||
|
||||
class TagCategoryProductSerializer(serializers.ModelSerializer):
|
||||
"""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:
|
||||
"""Meta class."""
|
||||
|
|
@ -56,7 +71,6 @@ class TagCategoryProductSerializer(serializers.ModelSerializer):
|
|||
model = models.TagCategory
|
||||
fields = (
|
||||
'id',
|
||||
'label_translated',
|
||||
'index_name',
|
||||
)
|
||||
|
||||
|
|
@ -64,8 +78,8 @@ class TagCategoryProductSerializer(serializers.ModelSerializer):
|
|||
class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for model TagCategory."""
|
||||
|
||||
label_translated = TranslatedField()
|
||||
tags = SerializerMethodField()
|
||||
tags = TagBaseSerializer(many=True, allow_null=True)
|
||||
label_translated = serializers.SerializerMethodField(read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -78,33 +92,17 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
'tags',
|
||||
)
|
||||
|
||||
def get_tags(self, obj):
|
||||
query_params = dict(self.context['request'].query_params)
|
||||
|
||||
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
|
||||
def get_label_translated(self, obj):
|
||||
return translate_obj(obj)
|
||||
|
||||
|
||||
class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for model TagCategory."""
|
||||
|
||||
label_translated = TranslatedField()
|
||||
filters = SerializerMethodField()
|
||||
param_name = SerializerMethodField()
|
||||
type = SerializerMethodField()
|
||||
label_translated = serializers.SerializerMethodField(allow_null=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
|
@ -127,6 +125,9 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
return 'wine_colors_id__in'
|
||||
return 'tags_id__in'
|
||||
|
||||
def get_label_translated(self, obj):
|
||||
return translate_obj(obj)
|
||||
|
||||
def get_fields(self, *args, **kwargs):
|
||||
fields = super(FiltersTagCategoryBaseSerializer, self).get_fields()
|
||||
|
||||
|
|
@ -157,10 +158,13 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
class TagCategoryShortSerializer(serializers.ModelSerializer):
|
||||
"""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',
|
||||
read_only=True)
|
||||
|
||||
def get_label_translated(self, obj):
|
||||
return translate_obj(obj)
|
||||
|
||||
class Meta(TagCategoryBaseSerializer.Meta):
|
||||
"""Meta class."""
|
||||
fields = [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from datetime import time
|
||||
from datetime import time, datetime
|
||||
|
||||
from utils.models import ProjectBaseMixin
|
||||
|
||||
|
|
@ -59,6 +59,14 @@ class Timetable(ProjectBaseMixin):
|
|||
def opening_at_str(self):
|
||||
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
|
||||
def opening_time(self):
|
||||
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 tag.models import Tag
|
||||
from translation.models import SiteInterfaceDictionary
|
||||
from transfer.mixins import TransferSerializerMixin
|
||||
from transfer.models import Cepages
|
||||
|
||||
|
|
@ -36,8 +37,11 @@ class AssemblageTagSerializer(TransferSerializerMixin):
|
|||
def create(self, validated_data):
|
||||
qs = self.Meta.model.objects.filter(**validated_data)
|
||||
category = validated_data.get('category')
|
||||
translations = validated_data.pop('label')
|
||||
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):
|
||||
if cepage and percent:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.apps import apps
|
||||
from utils.models import ProjectBaseMixin, LocaleManagerMixin
|
||||
|
||||
|
||||
class LanguageQuerySet(models.QuerySet):
|
||||
"""QuerySet for model Language"""
|
||||
|
||||
|
|
@ -50,6 +50,44 @@ class Language(models.Model):
|
|||
class SiteInterfaceDictionaryManager(LocaleManagerMixin):
|
||||
"""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):
|
||||
"""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 translate
|
||||
|
||||
|
||||
# todo: refactor this
|
||||
class IndexJSON:
|
||||
|
||||
|
|
@ -443,7 +442,8 @@ class HasTagsMixin(models.Model):
|
|||
|
||||
@property
|
||||
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')
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -516,8 +516,12 @@ PHONENUMBER_DEFAULT_REGION = "FR"
|
|||
|
||||
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']
|
||||
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']
|
||||
|
||||
THUMBNAIL_ENGINE = 'utils.thumbnail_engine.GMEngine'
|
||||
|
|
|
|||
|
|
@ -86,11 +86,11 @@ LOGGING = {
|
|||
'py.warnings': {
|
||||
'handlers': ['console'],
|
||||
},
|
||||
# 'django.db.backends': {
|
||||
# 'handlers': ['console', ],
|
||||
# 'level': 'DEBUG',
|
||||
# 'propagate': False,
|
||||
# },
|
||||
'django.db.backends': {
|
||||
'handlers': ['console', ],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,3 +63,6 @@ pycountry==19.8.18
|
|||
|
||||
# sql-tree
|
||||
django-mptt==0.9.1
|
||||
|
||||
# Export to Excel
|
||||
XlsxWriter==1.2.6
|
||||
Loading…
Reference in New Issue
Block a user