Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop

This commit is contained in:
Dmitriy Kuzmenko 2020-01-09 17:47:26 +03:00
commit 807f8b4169
25 changed files with 669 additions and 250 deletions

View File

@ -8,6 +8,7 @@ from rest_framework import serializers
from rest_framework import validators as rest_validators from rest_framework import validators as rest_validators
from account import models, tasks from account import models, tasks
from notification.models import Subscriber
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils import methods as utils_methods from utils import methods as utils_methods
@ -46,6 +47,12 @@ class UserSerializer(serializers.ModelSerializer):
'newsletter', 'newsletter',
] ]
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
validated_data['user'] = user
Subscriber.objects.make_subscriber(**validated_data)
return user
def validate_email(self, value): def validate_email(self, value):
"""Validate email value""" """Validate email value"""
if value == self.instance.email: if value == self.instance.email:

View File

@ -848,10 +848,13 @@ class GuideElementManager(models.Manager):
if product_qs.exists() and wine_region_node_qs.exists(): if product_qs.exists() and wine_region_node_qs.exists():
wine_region_node = wine_region_node_qs.first() wine_region_node = wine_region_node_qs.first()
root_node = wine_region_node.get_root() root_node = wine_region_node.get_root()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(), product = product_qs.first()
parent=wine_region_node,
guide=root_node.guide, if product.establishment:
product=product_qs.first()) return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=wine_region_node,
guide=root_node.guide,
establishment=product.establishment)
return None, False return None, False
def get_or_create_color_wine_section_node(self, wine_color_name: str, yard_node_id: int): def get_or_create_color_wine_section_node(self, wine_color_name: str, yard_node_id: int):
@ -869,7 +872,7 @@ class GuideElementManager(models.Manager):
}) })
if parent_node_qs.exists() and guide_element_type_qs.exists(): if parent_node_qs.exists() and guide_element_type_qs.exists():
root_node = parent_node_qs.first().get_root() root_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(), return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=root_node, parent=root_node,
wine_color_section=wine_color_section, wine_color_section=wine_color_section,
@ -884,7 +887,7 @@ class GuideElementManager(models.Manager):
review_qs = Review.objects.filter(id=review_id) review_qs = Review.objects.filter(id=review_id)
if parent_node_qs.exists() and wine_qs.exists() and review_qs.exists() and guide_element_type_qs.exists(): if parent_node_qs.exists() and wine_qs.exists() and review_qs.exists() and guide_element_type_qs.exists():
root_node = parent_node_qs.first().get_root() root_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(), return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=root_node, parent=root_node,
product=wine_qs.first(), product=wine_qs.first(),
@ -973,8 +976,3 @@ class GuideElement(ProjectBaseMixin, MPTTModel):
def __str__(self): def __str__(self):
"""Overridden dunder method.""" """Overridden dunder method."""
return self.guide_element_type.name if self.guide_element_type else self.id return self.guide_element_type.name if self.guide_element_type else self.id
@property
def advertorial_page(self):
if self.advertorial:
return self.advertorial.right_pages

View File

@ -152,6 +152,8 @@ class GuideElementBaseSerializer(serializers.ModelSerializer):
allow_null=True) allow_null=True)
wine_color_section_name = serializers.CharField(source='wine_color_section.name', wine_color_section_name = serializers.CharField(source='wine_color_section.name',
allow_null=True) allow_null=True)
wine_region_name = serializers.CharField(source='wine_region.name',
allow_null=True)
node_name = serializers.CharField(source='guide_element_type.name') node_name = serializers.CharField(source='guide_element_type.name')
label_photo = serializers.ImageField(source='label_photo.image', allow_null=True) label_photo = serializers.ImageField(source='label_photo.image', allow_null=True)
city_name = serializers.CharField(source='city.name', allow_null=True) city_name = serializers.CharField(source='city.name', allow_null=True)
@ -167,12 +169,12 @@ class GuideElementBaseSerializer(serializers.ModelSerializer):
'node_name', 'node_name',
'establishment_detail', 'establishment_detail',
'review', 'review',
'wine_region',
'product_detail', 'product_detail',
'priority', 'priority',
'city_name', 'city_name',
'section_name', 'section_name',
'wine_color_section_name', 'wine_color_section_name',
'wine_region_name',
'children', 'children',
'label_photo', 'label_photo',
] ]
@ -237,19 +239,23 @@ class GuideElementExportSerializer(GuideElementBaseSerializer):
default=None) default=None)
metadata = serializers.ListField(source='establishment.metadata', metadata = serializers.ListField(source='establishment.metadata',
default=None) default=None)
advertorial_page = serializers.IntegerField(default=None) advertorial = serializers.DictField(source='advertorial.__dict__', default=None)
# PRODUCT # PRODUCT
product_name = serializers.CharField(source='product.name', product_name = serializers.CharField(source='product.name',
default=None) default=None)
product_review = serializers.DictField(source='product.establishment.last_published_review_data', product_review = serializers.DictField(source='product.last_published_review_data',
default=None) default=None)
product_type = serializers.CharField(source='product.product_type_label', product_type = serializers.CharField(source='product.product_type.label',
default=None) default=None)
product_subtypes = serializers.CharField(source='product.product_subtype_labels', product_subtypes = serializers.CharField(source='product.product_subtype_labels',
default=None) default=None)
product_city = serializers.CharField(source='product.establishment.address.city.name',
default=None)
product_address = serializers.CharField(source='product.establishment.address.full_address', product_address = serializers.CharField(source='product.establishment.address.full_address',
default=None) default=None)
product_metadata = serializers.ListField(source='product.metadata',
default=None)
class Meta: class Meta:
model = models.GuideElement model = models.GuideElement
@ -257,16 +263,8 @@ class GuideElementExportSerializer(GuideElementBaseSerializer):
'id', 'id',
'guide', 'guide',
'node_name', 'node_name',
# 'establishment',
# 'review',
# 'wine_region',
# 'product_detail',
# 'priority',
'city_name', 'city_name',
# 'section_name', 'wine_color_section_name',
# 'wine_color_section_name',
# 'children',
'label_photo_url',
'name', 'name',
'public_mark', 'public_mark',
'toque_number', 'toque_number',
@ -278,5 +276,12 @@ class GuideElementExportSerializer(GuideElementBaseSerializer):
'review', 'review',
'price_level', 'price_level',
'metadata', 'metadata',
'advertorial_page', 'advertorial',
'product_name',
'product_review',
'product_type',
'product_subtypes',
'product_address',
'product_city',
'product_metadata',
] ]

View File

@ -148,10 +148,11 @@ def export_guide(guide_id, user_id, file_type='csv'):
guide = Guide.objects.get(id=guide_id) guide = Guide.objects.get(id=guide_id)
root = GuideElement.objects.get_root_node(guide) root = GuideElement.objects.get_root_node(guide)
if root: if root:
nodes = root.get_descendants().select_related('review', 'establishment', 'wine_region', nodes = root.get_descendants().select_related(
'product', 'city', 'wine_color_section', 'review', 'establishment', 'wine_region',
'section', 'label_photo', 'guide', 'product', 'city', 'wine_color_section',
'city__country', 'establishment__establishment_type') 'section', 'label_photo', 'guide',
'city__country', 'establishment__establishment_type')
serializer = GuideElementExportSerializer(nodes, many=True) serializer = GuideElementExportSerializer(nodes, many=True)
data = serializer.data data = serializer.data
SendGuideExport( SendGuideExport(

View File

@ -186,16 +186,19 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
"""Get last list actual public_mark""" """Get last list actual public_mark"""
qs = obj.establishmentemployee_set.actual().order_by('-from_date')\ qs = obj.establishmentemployee_set.actual().order_by('-from_date')\
.values('establishment__public_mark').first() .values('establishment__public_mark').first()
return qs['establishment__public_mark'] return qs['establishment__public_mark'] if qs else None
def get_positions(self, obj): def get_positions(self, obj):
"""Get last list actual positions""" """Get last list actual positions"""
est_id = obj.establishmentemployee_set.actual().\ est_id = obj.establishmentemployee_set.actual().\
order_by('-from_date').first().establishment_id order_by('-from_date').first()
if not est_id:
return None
qs = obj.establishmentemployee_set.actual()\ qs = obj.establishmentemployee_set.actual()\
.filter(establishment_id=est_id)\ .filter(establishment_id=est_id.establishment_id)\
.prefetch_related('position').values('position') .prefetch_related('position').values('position')
positions = models.Position.objects.filter(id__in=[q['position'] for q in qs]) positions = models.Position.objects.filter(id__in=[q['position'] for q in qs])
@ -205,11 +208,14 @@ class EmployeeBackSerializers(serializers.ModelSerializer):
def get_establishment(self, obj): def get_establishment(self, obj):
"""Get last actual establishment""" """Get last actual establishment"""
est = obj.establishmentemployee_set.actual().order_by('-from_date')\ est = obj.establishmentemployee_set.actual().order_by('-from_date')\
.first().establishment .first()
if not est:
return None
return { return {
"id": est.id, "id": est.establishment.id,
"slug": est.slug "slug": est.establishment.slug
} }
class Meta: class Meta:

View File

@ -1,43 +1,60 @@
from datetime import datetime from datetime import datetime
from smtplib import SMTPException
from celery import shared_task from celery import shared_task
from django.core.mail import send_mail
from notification.models import Subscriber
from news import models
from django.template.loader import render_to_string, get_template
from django.conf import settings from django.conf import settings
from smtplib import SMTPException from django.core.mail import send_mail
from django.core.validators import EMPTY_VALUES from django.core.validators import EMPTY_VALUES
from django.template.loader import get_template, render_to_string
from main.models import SiteSettings from main.models import SiteSettings
from news import models
from notification.models import Subscribe
@shared_task @shared_task
def send_email_with_news(news_ids): def send_email_with_news(news_ids):
subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) subscribes = Subscribe.objects.all() \
.prefetch_related('subscriber') \
.prefetch_related('subscription_type')
sent_news = models.News.objects.filter(id__in=news_ids) sent_news = models.News.objects.filter(id__in=news_ids)
htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) htmly = get_template(settings.NEWS_EMAIL_TEMPLATE)
year = datetime.now().year year = datetime.now().year
socials = list(SiteSettings.objects.with_country())
socials = dict(zip(map(lambda s: s.country.code, socials), socials)) socials = list(SiteSettings.objects.with_country().select_related('country'))
for s in subscribers: socials = dict(zip(map(lambda social: social.country.code, socials), socials))
socials_for_subscriber = socials.get(s.country_code)
for subscribe in subscribes.filter(unsubscribe_date=None):
country = subscribe.subscription_type.country
if country is None:
continue
socials_for_subscriber = socials.get(country.code)
subscriber = subscribe.subscriber
try: try:
for n in sent_news: for new in sent_news:
context = {"title": n.title.get(s.locale), context = {
"subtitle": n.subtitle.get(s.locale), "title": new.title.get(subscriber.locale),
"description": n.description.get(s.locale), "subtitle": new.subtitle.get(subscriber.locale),
"code": s.update_code, "description": new.description.get(subscriber.locale),
"image_url": n.image_url if n.image_url not in EMPTY_VALUES else None, "code": subscriber.update_code,
"domain_uri": settings.DOMAIN_URI, "image_url": new.image_url if new.image_url not in EMPTY_VALUES else None,
"slug": n.slug, "domain_uri": settings.DOMAIN_URI,
"country_code": s.country_code, "slug": new.slug,
"twitter_page_url": socials_for_subscriber.twitter_page_url if socials_for_subscriber else '#', "country_code": subscriber.country_code,
"instagram_page_url": socials_for_subscriber.instagram_page_url if socials_for_subscriber else '#', "twitter_page_url": socials_for_subscriber.twitter_page_url if socials_for_subscriber else '#',
"facebook_page_url": socials_for_subscriber.facebook_page_url if socials_for_subscriber else '#', "instagram_page_url": socials_for_subscriber.instagram_page_url if socials_for_subscriber else '#',
"send_to": s.send_to, "facebook_page_url": socials_for_subscriber.facebook_page_url if socials_for_subscriber else '#',
"year": year} "send_to": subscriber.send_to,
send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context), "year": year
settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False, }
html_message=htmly.render(context)) send_mail(
"G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context),
settings.EMAIL_HOST_USER, [subscriber.send_to], fail_silently=False,
html_message=htmly.render(context)
)
except SMTPException: except SMTPException:
continue continue

View File

@ -145,7 +145,7 @@ def add_tags():
) )
if created: if created:
text_value = ' '.join(new_tag.value.split('_')) text_value = ' '.join(new_tag.value.split('_'))
translation = SiteInterfaceDictionary(page={'en-GB': text_value}, keywords=f'tag.{new_tag.category}.{new_tag.value}') translation = SiteInterfaceDictionary(text={'en-GB': text_value}, keywords=f'tag.{new_tag.category}.{new_tag.value}')
translation.save() translation.save()
new_tag.translation = translation new_tag.translation = translation
new_tag.save() new_tag.save()

View File

@ -2,17 +2,17 @@
from django.urls import path from django.urls import path
from news import views from news import views
from search_indexes.views import NewsDocumentViewSet
app_name = 'news' app_name = 'news'
urlpatterns = [ urlpatterns = [
path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('', views.NewsBackOfficeLCView.as_view(), name='list-create'),
path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(), path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'),
name='retrieve-update-destroy'), path('<int:pk>/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'),
path('<int:pk>/gallery/', views.NewsBackOfficeGalleryListView.as_view(),
name='gallery-list'),
path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), path('<int:pk>/gallery/<int:image_id>/', views.NewsBackOfficeGalleryCreateDestroyView.as_view(),
name='gallery-create-destroy'), name='gallery-create-destroy'),
path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'), path('<int:pk>/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'),
path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='clone-news-item'), path('<int:pk>/clone/<str:country_code>', views.NewsCloneView.as_view(), name='clone-news-item'),
path('search/', NewsDocumentViewSet.as_view({'get': 'list'}), name='search-news'),
] ]

View File

@ -0,0 +1,38 @@
# Generated by Django 2.2.7 on 2019-12-27 12:12
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('notification', '0004_auto_20191118_1307'),
]
operations = [
migrations.RemoveField(
model_name='subscriber',
name='subscription_type',
),
migrations.CreateModel(
name='Subscribe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('subscriber', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notification.Subscriber')),
('subscription_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notification.SubscriptionType')),
],
options={
'verbose_name': 'Subscribe',
'verbose_name_plural': 'Subscribes',
},
),
migrations.AddField(
model_name='subscriber',
name='subscription_types',
field=models.ManyToManyField(through='notification.Subscribe', to='notification.SubscriptionType'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.7 on 2019-12-27 12:16
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('notification', '0005_auto_20191227_1212'),
]
operations = [
migrations.AddField(
model_name='subscribe',
name='subscribe_date',
field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True, verbose_name='Last subscribe date'),
),
migrations.AddField(
model_name='subscribe',
name='unsubscribe_date',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Last unsubscribe date'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.2.7 on 2019-12-27 14:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notification', '0006_auto_20191227_1216'),
]
operations = [
migrations.RemoveField(
model_name='subscriber',
name='state',
),
migrations.AddField(
model_name='subscribe',
name='state',
field=models.PositiveIntegerField(choices=[(0, 'Unusable'), (1, 'Usable')], default=1, verbose_name='State'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.2.7 on 2019-12-30 15:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('notification', '0007_auto_20191227_1426'),
]
operations = [
migrations.RemoveField(
model_name='subscribe',
name='state',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.7 on 2019-12-31 01:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('location', '0033_merge_20191224_0920'),
('notification', '0008_remove_subscribe_state'),
]
operations = [
migrations.AddField(
model_name='subscriptiontype',
name='country',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2019-12-31 01:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('notification', '0009_subscriptiontype_country'),
]
operations = [
migrations.AlterField(
model_name='subscriptiontype',
name='country',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='country'),
),
]

View File

@ -1,10 +1,14 @@
"""Notification app models.""" """Notification app models."""
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from account.models import User from account.models import User
from location.models import Country
from utils.methods import generate_string_code from utils.methods import generate_string_code
from utils.models import ProjectBaseMixin, TranslatedFieldsMixin, TJSONField from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin
class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin): class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin):
@ -12,6 +16,9 @@ class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin):
name = TJSONField(blank=True, null=True, default=None, name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('name'), verbose_name=_('name'),
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
country = models.ForeignKey(Country, on_delete=models.PROTECT,
blank=True, null=True, default=None,
verbose_name=_('country'))
# todo: associate user & subscriber after users registration # todo: associate user & subscriber after users registration
@ -19,7 +26,7 @@ class SubscriberManager(models.Manager):
"""Extended manager for Subscriber model.""" """Extended manager for Subscriber model."""
def make_subscriber(self, email=None, user=None, ip_address=None, country_code=None, def make_subscriber(self, email=None, user=None, ip_address=None, country_code=None,
locale=None, subscription_type=None, *args, **kwargs): locale=None, subscription_types=None, *args, **kwargs):
"""Make subscriber and update info.""" """Make subscriber and update info."""
# search existing object # search existing object
if not user: if not user:
@ -40,14 +47,15 @@ class SubscriberManager(models.Manager):
obj.ip_address = ip_address obj.ip_address = ip_address
obj.country_code = country_code obj.country_code = country_code
obj.locale = locale obj.locale = locale
obj.state = self.model.USABLE
obj.update_code = generate_string_code() obj.update_code = generate_string_code()
obj.subscription_type = subscription_type
obj.save() obj.save()
obj.subscription_types.set(subscription_types)
obj.subscribe_set.update(unsubscribe_date=None)
else: else:
obj = self.model.objects.create(user=user, email=email, ip_address=ip_address, obj = self.model.objects.create(user=user, email=email, ip_address=ip_address,
country_code=country_code, locale=locale, country_code=country_code, locale=locale)
subscription_type=subscription_type) obj.subscription_types.set(subscription_types)
obj.subscribe_set.update(unsubscribe_date=None)
return obj return obj
def associate_user(self, user): def associate_user(self, user):
@ -62,27 +70,9 @@ class SubscriberManager(models.Manager):
return obj return obj
class SubscriberQuerySet(models.QuerySet):
"""Extended queryset for Subscriber model."""
def by_usable(self, switcher=True):
if switcher:
return self.filter(state=self.model.USABLE)
else:
return self.filter(state=self.model.UNUSABLE)
class Subscriber(ProjectBaseMixin): class Subscriber(ProjectBaseMixin):
"""Subscriber model.""" """Subscriber model."""
UNUSABLE = 0
USABLE = 1
STATE_CHOICES = (
(UNUSABLE, _('Unusable')),
(USABLE, _('Usable')),
)
user = models.ForeignKey( user = models.ForeignKey(
User, User,
blank=True, blank=True,
@ -96,7 +86,6 @@ class Subscriber(ProjectBaseMixin):
ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, verbose_name=_('IP address')) ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, verbose_name=_('IP address'))
country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('Country code')) country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('Country code'))
locale = models.CharField(blank=True, null=True, default=None, max_length=10, verbose_name=_('Locale identifier')) locale = models.CharField(blank=True, null=True, default=None, max_length=10, verbose_name=_('Locale identifier'))
state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State'))
update_code = models.CharField( update_code = models.CharField(
max_length=254, max_length=254,
blank=True, blank=True,
@ -107,9 +96,9 @@ class Subscriber(ProjectBaseMixin):
) )
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE, null=True, default=None) subscription_types = models.ManyToManyField(SubscriptionType, through='Subscribe')
objects = SubscriberManager.from_queryset(SubscriberQuerySet)() objects = SubscriberManager()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -123,10 +112,9 @@ class Subscriber(ProjectBaseMixin):
self.update_code = generate_string_code() self.update_code = generate_string_code()
return super(Subscriber, self).save(*args, **kwargs) return super(Subscriber, self).save(*args, **kwargs)
def unsubscribe(self): def unsubscribe(self, query: dict):
"""Unsubscribe user.""" """Unsubscribe user."""
self.state = self.UNUSABLE self.subscribe_set.update(unsubscribe_date=now())
self.save()
@property @property
def send_to(self): def send_to(self):
@ -141,3 +129,19 @@ class Subscriber(ProjectBaseMixin):
url = settings.SITE_REDIRECT_URL_UNSUBSCRIBE url = settings.SITE_REDIRECT_URL_UNSUBSCRIBE
query = f'?code={self.update_code}' query = f'?code={self.update_code}'
return f'{schema}://{site_domain}{url}{query}' return f'{schema}://{site_domain}{url}{query}'
class Subscribe(ProjectBaseMixin):
"""Subscribe model."""
subscribe_date = models.DateTimeField(_('Last subscribe date'), blank=True, null=True, default=now)
unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None)
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE)
subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE)
class Meta:
"""Meta class."""
verbose_name = _('Subscribe')
verbose_name_plural = _('Subscribes')

View File

@ -1,6 +1,8 @@
"""Notification app serializers.""" """Notification app serializers."""
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from location.serializers import CountrySimpleSerializer
from notification import models from notification import models
from utils.methods import get_user_ip from utils.methods import get_user_ip
from utils.serializers import TranslatedField from utils.serializers import TranslatedField
@ -10,6 +12,8 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer):
"""Subscription type serializer.""" """Subscription type serializer."""
name_translated = TranslatedField() name_translated = TranslatedField()
country = CountrySimpleSerializer()
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -18,14 +22,15 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer):
'id', 'id',
'index_name', 'index_name',
'name_translated', 'name_translated',
'country',
) )
class SubscribeSerializer(serializers.ModelSerializer): class CreateSubscribeSerializer(serializers.ModelSerializer):
"""Subscribe serializer.""" """Create Subscribe serializer."""
email = serializers.EmailField(required=False, source='send_to') email = serializers.EmailField(required=False, source='send_to')
subscription_type = SubscriptionTypeSerializer(read_only=True) subscription_types = serializers.PrimaryKeyRelatedField(many=True, queryset=models.SubscriptionType.objects.all())
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -33,10 +38,9 @@ class SubscribeSerializer(serializers.ModelSerializer):
model = models.Subscriber model = models.Subscriber
fields = ( fields = (
'email', 'email',
'subscription_type', 'subscription_types',
'state', 'link_to_unsubscribe',
) )
read_only_fields = ('state',)
def validate(self, attrs): def validate(self, attrs):
"""Validate attrs.""" """Validate attrs."""
@ -45,6 +49,10 @@ class SubscribeSerializer(serializers.ModelSerializer):
# validate email # validate email
email = attrs.get('send_to') email = attrs.get('send_to')
if attrs.get('email'):
email = attrs.get('email')
if user.is_authenticated: if user.is_authenticated:
if email is not None and email != user.email: if email is not None and email != user.email:
raise serializers.ValidationError(_('Does not match user email')) raise serializers.ValidationError(_('Does not match user email'))
@ -57,18 +65,40 @@ class SubscribeSerializer(serializers.ModelSerializer):
attrs['country_code'] = request.country_code attrs['country_code'] = request.country_code
attrs['locale'] = request.locale attrs['locale'] = request.locale
attrs['ip_address'] = get_user_ip(request) attrs['ip_address'] = get_user_ip(request)
if user.is_authenticated: if user.is_authenticated:
attrs['user'] = user attrs['user'] = user
subscription_type_id = self.context.get('request').parser_context.get('kwargs').get("subscription_type_pk")
subscription_type_qs = models.SubscriptionType.objects.filter(id=subscription_type_id)
if not subscription_type_qs.exists():
raise serializers.ValidationError({'detail': _(f'SubscriptionType not found.')})
attrs["subscription_type"] = subscription_type_qs.first()
return attrs return attrs
def create(self, validated_data): def create(self, validated_data):
"""Create obj.""" """Create obj."""
subscriber = models.Subscriber.objects.make_subscriber(**validated_data) return models.Subscriber.objects.make_subscriber(**validated_data)
return subscriber
class SubscribeObjectSerializer(serializers.ModelSerializer):
"""Subscribe serializer."""
class Meta:
"""Meta class."""
model = models.Subscriber
fields = ('subscriber', )
read_only_fields = ('subscribe_date', 'unsubscribe_date',)
class SubscribeSerializer(serializers.ModelSerializer):
"""Subscribe serializer."""
email = serializers.EmailField(required=False, source='send_to')
subscription_types = SubscriptionTypeSerializer(many=True, read_only=True)
class Meta:
"""Meta class."""
model = models.Subscriber
fields = (
'email',
'subscription_types',
'link_to_unsubscribe',
)

View File

@ -1,11 +1,15 @@
from datetime import datetime, timedelta
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from django.test import TestCase
from rest_framework.test import APITestCase
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase
from account.models import User from account.models import Role, User, UserRole
from notification.models import Subscriber from location.models import Country
from main.models import SiteSettings
from news.models import News, NewsType
from notification.models import Subscriber, SubscriptionType
from translation.models import Language
class BaseTestCase(APITestCase): class BaseTestCase(APITestCase):
@ -17,23 +21,22 @@ class BaseTestCase(APITestCase):
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
# get tokens # get tokens
tokens = User.create_jwt_tokens(self.user) tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie({'access_token': tokens.get('access_token'), self.client.cookies = SimpleCookie({
'refresh_token': tokens.get('refresh_token')}) 'access_token': tokens.get('access_token'),
'refresh_token': tokens.get('refresh_token')
})
class NotificationAnonSubscribeTestCase(APITestCase): class NotificationAnonSubscribeTestCase(APITestCase):
def test_subscribe(self): def test_subscribe(self):
test_data = { test_data = {
"email": "test@email.com", "email": "test@email.com"
"state": 1
} }
response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json") response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["email"], test_data["email"]) self.assertEqual(response.json()["email"], test_data["email"])
self.assertEqual(response.json()["state"], test_data["state"])
class NotificationSubscribeTestCase(BaseTestCase): class NotificationSubscribeTestCase(BaseTestCase):
@ -42,21 +45,17 @@ class NotificationSubscribeTestCase(BaseTestCase):
super().setUp() super().setUp()
self.test_data = { self.test_data = {
"email": self.email, "email": self.email
"state": 1
} }
def test_subscribe(self): def test_subscribe(self):
response = self.client.post("/api/web/notifications/subscribe/", data=self.test_data, format="json") response = self.client.post("/api/web/notifications/subscribe/", data=self.test_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["email"], self.email) self.assertEqual(response.json()["email"], self.email)
self.assertEqual(response.json()["state"], self.test_data["state"])
def test_subscribe_info_auth_user(self): def test_subscribe_info_auth_user(self):
Subscriber.objects.create(user=self.user, email=self.email)
Subscriber.objects.create(user=self.user, email=self.email, state=1)
response = self.client.get("/api/web/notifications/subscribe-info/", data=self.test_data, format="json") response = self.client.get("/api/web/notifications/subscribe-info/", data=self.test_data, format="json")
@ -66,13 +65,12 @@ class NotificationSubscribeTestCase(BaseTestCase):
class NotificationSubscribeInfoTestCase(APITestCase): class NotificationSubscribeInfoTestCase(APITestCase):
def test_subscribe_info(self): def test_subscribe_info(self):
self.username = 'sedragurda' self.username = 'sedragurda'
self.password = 'sedragurdaredips19' self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com' self.email = 'sedragurda@desoz.com'
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) test_subscriber = Subscriber.objects.create(user=self.user, email=self.email)
response = self.client.get(f"/api/web/notifications/subscribe-info/{test_subscriber.update_code}/") response = self.client.get(f"/api/web/notifications/subscribe-info/{test_subscriber.update_code}/")
@ -82,12 +80,10 @@ class NotificationSubscribeInfoTestCase(APITestCase):
class NotificationUnsubscribeAuthUserTestCase(BaseTestCase): class NotificationUnsubscribeAuthUserTestCase(BaseTestCase):
def test_unsubscribe_auth_user(self): def test_unsubscribe_auth_user(self):
Subscriber.objects.create(user=self.user, email=self.email)
Subscriber.objects.create(user=self.user, email=self.email, state=1)
self.test_data = { self.test_data = {
"email": self.email, "email": self.email
"state": 1
} }
response = self.client.patch("/api/web/notifications/unsubscribe/", data=self.test_data, format="json") response = self.client.patch("/api/web/notifications/unsubscribe/", data=self.test_data, format="json")
@ -104,13 +100,102 @@ class NotificationUnsubscribeTestCase(APITestCase):
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
self.test_data = { self.test_data = {
"email": self.email, "email": self.email
"state": 1
} }
test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) test_subscriber = Subscriber.objects.create(user=self.user, email=self.email)
response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/", response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/",
data=self.test_data, format="json") data=self.test_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
class NotificationManySubscribeTestCase(APITestCase):
def test_subscribe(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.user = User.objects.create_user(
username=self.username, email=self.email, password=self.password)
# get tokens
tokens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie(
{
'access_token': tokens.get('access_token'),
'refresh_token': tokens.get('refresh_token')
})
self.test_news_type = NewsType.objects.create(name="Test news type")
self.lang, created = Language.objects.get_or_create(
title='Russia',
locale='ru-RU'
)
self.country_ru, created = Country.objects.get_or_create(
name={"en-GB": "Russian"}
)
self.site_ru, created = SiteSettings.objects.get_or_create(
subdomain='ru'
)
role = Role.objects.create(
role=Role.CONTENT_PAGE_MANAGER,
site_id=self.site_ru.id
)
role.save()
user_role = UserRole.objects.create(
user=self.user,
role=role
)
user_role.save()
self.test_news = News.objects.create(
created_by=self.user, modified_by=self.user,
title={"ru-RU": "Test news"},
news_type=self.test_news_type,
description={"ru-RU": "Description test news"},
end=datetime.now() + timedelta(hours=2),
state=News.PUBLISHED,
slugs={'en-GB': 'test-news-slug'},
country=self.country_ru,
site=self.site_ru
)
self.test_subscribe_type = SubscriptionType.objects.create(
index_name='test_index_name',
name={"ru-RU": "Test subscription type"}
)
test_data = {
'email': self.email,
'subscription_types_pk': [
self.test_subscribe_type.id
]
}
response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["email"], test_data["email"])
def test_unsubscribe(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.user = User.objects.create_user(
username=self.username, email=self.email, password=self.password)
test_data = {
"email": self.email
}
test_subscriber = Subscriber.objects.create(user=self.user, email=self.email)
response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/",
data=test_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -0,0 +1 @@
urlpatterns = []

View File

@ -5,7 +5,7 @@ from notification.views import common
app_name = "notification" app_name = "notification"
urlpatterns = [ urlpatterns = [
path('subscribe/<int:subscription_type_pk>', common.SubscribeView.as_view(), name='subscribe'), path('subscribe/', common.CreateSubscribeView.as_view(), name='create-subscribe'),
path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'), path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'),
path('subscribe-info/<code>/', common.SubscribeInfoView.as_view(), name='check-code'), path('subscribe-info/<code>/', common.SubscribeInfoView.as_view(), name='check-code'),
path('unsubscribe/', common.UnsubscribeAuthUserView.as_view(), name='unsubscribe-auth'), path('unsubscribe/', common.UnsubscribeAuthUserView.as_view(), name='unsubscribe-auth'),

View File

@ -2,22 +2,17 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions from rest_framework import generics, permissions
from rest_framework.response import Response from rest_framework.response import Response
from notification import models from notification import models
from notification.serializers import common as serializers from notification.serializers import common as serializers
class SubscribeView(generics.GenericAPIView): class CreateSubscribeView(generics.CreateAPIView):
"""Subscribe View.""" """Create subscribe View."""
queryset = models.Subscriber.objects.all() queryset = models.Subscriber.objects.all()
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.SubscribeSerializer serializer_class = serializers.CreateSubscribeSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(data=serializer.data)
class SubscribeInfoView(generics.RetrieveAPIView): class SubscribeInfoView(generics.RetrieveAPIView):
@ -25,7 +20,7 @@ class SubscribeInfoView(generics.RetrieveAPIView):
lookup_field = 'update_code' lookup_field = 'update_code'
lookup_url_kwarg = 'code' lookup_url_kwarg = 'code'
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
queryset = models.Subscriber.objects.all() queryset = models.Subscriber.objects.all()
serializer_class = serializers.SubscribeSerializer serializer_class = serializers.SubscribeSerializer
@ -33,7 +28,7 @@ class SubscribeInfoView(generics.RetrieveAPIView):
class SubscribeInfoAuthUserView(generics.ListAPIView): class SubscribeInfoAuthUserView(generics.ListAPIView):
"""Subscribe info auth user view.""" """Subscribe info auth user view."""
permission_classes = (permissions.IsAuthenticated, ) permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.SubscribeSerializer serializer_class = serializers.SubscribeSerializer
def get_queryset(self): def get_queryset(self):
@ -42,18 +37,18 @@ class SubscribeInfoAuthUserView(generics.ListAPIView):
return queryset.filter(user=user) return queryset.filter(user=user)
class UnsubscribeView(generics.GenericAPIView): class UnsubscribeView(generics.UpdateAPIView):
"""Unsubscribe view.""" """Unsubscribe view."""
lookup_field = 'update_code' lookup_field = 'update_code'
lookup_url_kwarg = 'code' lookup_url_kwarg = 'code'
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
queryset = models.Subscriber.objects.all() queryset = models.Subscriber.objects.all()
serializer_class = serializers.SubscribeSerializer serializer_class = serializers.SubscribeSerializer
def patch(self, request, *args, **kw): def patch(self, request, *args, **kw):
obj = self.get_object() obj = self.get_object()
obj.unsubscribe() obj.unsubscribe(request.query_params)
serializer = self.get_serializer(instance=obj) serializer = self.get_serializer(instance=obj)
return Response(data=serializer.data) return Response(data=serializer.data)
@ -61,14 +56,14 @@ class UnsubscribeView(generics.GenericAPIView):
class UnsubscribeAuthUserView(generics.GenericAPIView): class UnsubscribeAuthUserView(generics.GenericAPIView):
"""Unsubscribe auth user view.""" """Unsubscribe auth user view."""
permission_classes = (permissions.IsAuthenticated, ) permission_classes = (permissions.IsAuthenticated,)
queryset = models.Subscriber.objects.all() queryset = models.Subscriber.objects.all()
serializer_class = serializers.SubscribeSerializer serializer_class = serializers.SubscribeSerializer
def patch(self, request, *args, **kw): def patch(self, request, *args, **kw):
user = request.user user = request.user
obj = get_object_or_404(models.Subscriber, user=user) obj = get_object_or_404(models.Subscriber, user=user)
obj.unsubscribe() obj.unsubscribe(request.query_params)
serializer = self.get_serializer(instance=obj) serializer = self.get_serializer(instance=obj)
return Response(data=serializer.data) return Response(data=serializer.data)
@ -78,4 +73,3 @@ class SubscriptionTypesView(generics.ListAPIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
queryset = models.SubscriptionType.objects.all() queryset = models.SubscriptionType.objects.all()
serializer_class = serializers.SubscriptionTypeSerializer serializer_class = serializers.SubscriptionTypeSerializer

View File

@ -58,6 +58,10 @@ class ProductType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin
verbose_name = _('Product type') verbose_name = _('Product type')
verbose_name_plural = _('Product types') verbose_name_plural = _('Product types')
@property
def label(self):
return transform_into_readable_str(self.index_name)
class ProductSubType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin): class ProductSubType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin):
"""ProductSubtype model.""" """ProductSubtype model."""
@ -410,6 +414,27 @@ class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes,
if self.wine_region: if self.wine_region:
return self.wine_region.name return self.wine_region.name
@property
def metadata(self):
if self.product_type:
metadata = []
tag_categories = (
self.product_type.tag_categories.exclude(index_name__in=[
'business_tag', 'purchased_item', 'accepted_payments_de',
'accepted_payments_hr', 'drinks', 'bottles_per_year',
'serial_number', 'surface', 'cooperative', 'tag',
'outside_sits', 'private_room']).values_list('index_name', flat=True)
)
for category in tag_categories:
tags = self.tags.filter(category__index_name=category).values_list('value', flat=True)
if tags.exists():
category_tags = {category: []}
for tag in tags:
category_tags[category].append(tag)
metadata.append(category_tags)
return metadata
class OnlineProductManager(ProductManager): class OnlineProductManager(ProductManager):
"""Extended manger for OnlineProduct model.""" """Extended manger for OnlineProduct model."""

View File

@ -10,10 +10,12 @@ class TagsBaseFilterSet(filters.FilterSet):
# Object type choices # Object type choices
NEWS = 'news' NEWS = 'news'
ESTABLISHMENT = 'establishment' ESTABLISHMENT = 'establishment'
RECIPES = 'recipe'
TYPE_CHOICES = ( TYPE_CHOICES = (
(NEWS, 'News'), (NEWS, 'News'),
(ESTABLISHMENT, 'Establishment'), (ESTABLISHMENT, 'Establishment'),
(RECIPES, 'Recipe'),
) )
type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES,
@ -91,5 +93,7 @@ class TagsFilterSet(TagsBaseFilterSet):
if self.ESTABLISHMENT in value: if self.ESTABLISHMENT in value:
queryset = queryset.for_establishments().filter(category__value_type='list').filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct( queryset = queryset.for_establishments().filter(category__value_type='list').filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
'value') 'value')
if self.RECIPES in value:
queryset = queryset.for_news().filter(value__in=settings.RECIPES_CHOSEN_TAGS).distinct('value')
return queryset return queryset

View File

@ -3,16 +3,14 @@ import csv
import logging import logging
import os import os
import tempfile import tempfile
import xml.etree.ElementTree as ET
from smtplib import SMTPException from smtplib import SMTPException
import docx import docx
import xlsxwriter import xlsxwriter
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from docx.blkcntnr import BlockItemContainer
from timetable.models import Timetable
from docx.shared import RGBColor, Pt from docx.shared import RGBColor, Pt
import xml.etree.ElementTree as ET
from utils.methods import section_name_into_index_name from utils.methods import section_name_into_index_name
@ -83,13 +81,12 @@ class DocTemplate:
def template(self, data: list): def template(self, data: list):
for instance in data: for obj in data:
instance = dict(instance) obj = dict(obj)
element_id = instance.get('id') index_name = section_name_into_index_name(obj.get('section_name'))
index_name = section_name_into_index_name(instance.get('section_name'))
# ESTABLISHMENT HEADING (LEVEL 1) # ESTABLISHMENT HEADING (LEVEL 1)
self.add_heading(name=instance['name'], self.add_heading(name=obj['name'],
font_style={'size': Pt(18), 'name': 'Palatino', 'bold': False}, font_style={'size': Pt(18), 'name': 'Palatino', 'bold': False},
level=1) level=1)
# ESTABLISHMENT TYPE PARAGRAPH # ESTABLISHMENT TYPE PARAGRAPH
@ -102,13 +99,13 @@ class DocTemplate:
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
level=2) level=2)
# CITY NAME HEADING (LEVEL 3) # CITY NAME HEADING (LEVEL 3)
self.add_heading(name=instance['city_name'], self.add_heading(name=obj['city_name'],
font_style={'size': Pt(12), 'name': 'Arial', 'bold': True, 'italic': True}, font_style={'size': Pt(12), 'name': 'Arial', 'bold': True, 'italic': True},
color_rgb=(102, 102, 102)) color_rgb=(102, 102, 102))
self.add_empty_line() self.add_empty_line()
# REVIEW HEADING (LEVEL 2) # REVIEW HEADING (LEVEL 2)
review = instance.get('review') review = obj.get('review')
if review: if review:
self.add_heading(name='Review', self.add_heading(name='Review',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -125,7 +122,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# PHONE HEADING (LEVEL 2) # PHONE HEADING (LEVEL 2)
phones = instance.get('phones') phones = obj.get('phones')
if phones: if phones:
self.add_heading(name='Phones', self.add_heading(name='Phones',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -135,18 +132,18 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# ADDRESS HEADING (LEVEL 2) # ADDRESS HEADING (LEVEL 2)
address = instance.get('address') address = obj.get('address')
if address: if address:
self.add_heading(name='Address', self.add_heading(name='Address',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
level=2) level=2)
# ADDRESS DATA PARAGRAPH # ADDRESS DATA PARAGRAPH
self.add_paragraph(name=instance.get('address'), self.add_paragraph(name=obj.get('address'),
font_style={'size': Pt(10), 'name': 'Arial'}) font_style={'size': Pt(10), 'name': 'Arial'})
self.add_empty_line() self.add_empty_line()
# TIMETABLE HEADING (LEVEL 2) # TIMETABLE HEADING (LEVEL 2)
schedule = instance.get('schedule') schedule = obj.get('schedule')
if schedule: if schedule:
self.add_heading(name='Schedule', self.add_heading(name='Schedule',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -160,7 +157,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# PUBLIC MARK HEADING (LEVEL 2) # PUBLIC MARK HEADING (LEVEL 2)
public_mark = instance.get('public_mark') public_mark = obj.get('public_mark')
if public_mark: if public_mark:
self.add_heading(name='Mark', self.add_heading(name='Mark',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -171,7 +168,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# TOQUE HEADING (LEVEL 2) # TOQUE HEADING (LEVEL 2)
toque = instance.get('toque_number') toque = obj.get('toque_number')
if toque: if toque:
self.add_heading(name='Toque', self.add_heading(name='Toque',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -182,7 +179,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# TOQUE HEADING (LEVEL 2) # TOQUE HEADING (LEVEL 2)
price_level = instance.get('price_level') price_level = obj.get('price_level')
if price_level: if price_level:
self.add_heading(name='Price level', self.add_heading(name='Price level',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -193,7 +190,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# SERVICES HEADING (LEVEL 2) # SERVICES HEADING (LEVEL 2)
services = instance.get('services') services = obj.get('services')
if services: if services:
self.add_heading(name='Services', self.add_heading(name='Services',
font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, font_style={'size': Pt(13), 'name': 'Arial', 'bold': True},
@ -203,7 +200,7 @@ class DocTemplate:
self.add_empty_line() self.add_empty_line()
# METADATA HEADING (LEVEL 2) # METADATA HEADING (LEVEL 2)
metadata = instance.get('metadata') metadata = obj.get('metadata')
if metadata: if metadata:
for obj in metadata: for obj in metadata:
for section, tags in obj.items(): for section, tags in obj.items():
@ -382,60 +379,28 @@ class SendGuideExport(SendExportBase):
name = self.guide.slug name = self.guide.slug
return f'export_{name}.{self.file_type}' return f'export_{name}.{self.file_type}'
def make_doc_file(self): def get_headers(self):
document = DocTemplate() headers = list(self.get_data()[0].keys())
document.template(self.get_doc_data()) headers.pop(headers.index('node_name'))
document.document.save(self.file_path)
self.success = True self.success = True
return headers
def get_data(self): def get_data(self):
return self.data excluded_data = ['guide', ]
if self.guide.guide_type in [self.guide.ARTISAN, self.guide.RESTAURANT]:
excluded_data.extend([
'product_name',
'product_review',
'product_type',
'product_subtypes',
'product_address',
'product_city',
'product_metadata',
'wine_color_section_name',
def get_doc_data(self): ])
init_data = self.get_data() elif self.guide.guide_type == self.guide.WINE:
objects = [] excluded_data.extend([
city_name = None
section_name = None
advertorial_page = None
for instance in init_data:
row_advertorial_page = instance.get('advertorial_page')
if row_advertorial_page:
advertorial_page = row_advertorial_page
else:
instance['advertorial_page'] = advertorial_page
row_city = instance.get('city_name')
if row_city:
city_name = row_city
else:
instance['city_name'] = city_name
row_section = instance.get('node_name')
if row_section.endswith('SectionNode'):
section_name = row_section
else:
instance['section_name'] = section_name
if instance.pop('node_name', None) == 'EstablishmentNode':
objects.append(instance.items())
return objects
def send(self):
self.get_file_method()
print(f'ok: {self.file_path}')
self.send_email()
def get_headers(self):
"""Get headers for model Establishment."""
exclude_headers = ['node_name', ]
headers = list(self.data[0].keys())
if self.guide.RESTAURANT or self.guide.ARTISAN:
exclude_headers.append('product_name', )
if self.guide.WINE:
exclude_headers.extend([
'name',
'public_mark', 'public_mark',
'toque_number', 'toque_number',
'schedule', 'schedule',
@ -446,13 +411,63 @@ class SendGuideExport(SendExportBase):
'review', 'review',
'price_level', 'price_level',
'metadata', 'metadata',
'public_mark',
'toque_number',
'schedule',
'phones',
'establishment_type',
'establishment_subtypes',
'city_name',
]) ])
for name in set(exclude_headers): for obj in self.data:
headers.pop(headers.index(name)) for column in excluded_data:
else: obj.pop(column) if column in obj.keys() else None
self.success = True return self.data
return headers
def get_doc_data(self):
init_data = self.get_data()
objects = []
city_name = None
section_name = None
ad_number_of_pages = None
ad_right_pages = None
for row in init_data:
row_advertorial = row.pop('advertorial')
if row_advertorial:
ad_number_of_pages = row_advertorial.get('number_of_pages')
ad_right_pages = row_advertorial.get('right_pages')
else:
row['ad_number_of_pages'] = ad_number_of_pages
row['ad_right_pages'] = ad_right_pages
row_city = row.get('city_name')
if row_city:
city_name = row_city
else:
row['city_name'] = city_name
row_section = row.get('node_name')
if row_section.endswith('SectionNode'):
section_name = row_section
else:
row['section_name'] = section_name
if row.pop('node_name', None) == 'EstablishmentNode':
objects.append(row.items())
return objects
def send(self):
self.get_file_method()
print(f'ok: {self.file_path}')
self.send_email()
def make_doc_file(self):
document = DocTemplate()
document.template(self.get_doc_data())
document.document.save(self.file_path)
self.success = True
def make_csv_file(self): def make_csv_file(self):
file_header = self.get_headers() file_header = self.get_headers()
@ -464,6 +479,9 @@ class SendGuideExport(SendExportBase):
# Write headers to CSV file # Write headers to CSV file
file_writer.writerow(file_header) file_writer.writerow(file_header)
city = None city = None
ad_number_of_pages = None
ad_right_pages = None
for row in self.get_data(): for row in self.get_data():
row_city = row.get('city_name') row_city = row.get('city_name')
if row_city: if row_city:
@ -471,29 +489,110 @@ class SendGuideExport(SendExportBase):
else: else:
row['city_name'] = city row['city_name'] = city
row_advertorial = row.get('advertorial')
if row_advertorial:
ad_number_of_pages = row_advertorial.get('number_of_pages')
ad_right_pages = row_advertorial.get('right_pages')
else:
row['ad_number_of_pages'] = ad_number_of_pages
row['ad_right_pages'] = ad_right_pages
if row.pop("node_name") == "EstablishmentNode": if row.pop("node_name") == "EstablishmentNode":
file_writer.writerow(row.values()) file_writer.writerow(row.values())
def make_xml_file(self): def make_xml_file(self):
# create the file structure if self.guide.guide_type == self.guide.WINE:
data = ET.Element('data') # products
items = ET.SubElement(data, 'items') city = None
city = None wine_color = None
for row in self.get_data(): establishment_name = None
row_city = row.get('city_name') establishment_address = None
if row_city: ad_number_of_pages = None
city = row_city ad_right_pages = None
else:
row['city_name'] = city
if row.pop("node_name") == "EstablishmentNode": # create the file structure
for key, value in row.items(): data = ET.Element('data')
item1 = ET.SubElement(items, 'item') products = ET.SubElement(data, 'products')
item1.set('name', key)
item1.text = str(value)
# create a new XML file with the results for row in self.get_data():
tree = ET.ElementTree(data) row_advertorial = row.pop('advertorial')
with open(self.file_path, 'bw') as f: if row_advertorial:
tree.write(f) ad_number_of_pages = row_advertorial.get('number_of_pages')
self.success = True ad_right_pages = row_advertorial.get('right_pages')
else:
row['ad_number_of_pages'] = ad_number_of_pages
row['ad_right_pages'] = ad_right_pages
row_establishment_address = row.get('address')
if row_establishment_address:
establishment_address = row_establishment_address
else:
row['establishment_name'] = establishment_address
row_establishment = row.pop('name')
if row_establishment:
establishment_name = row_establishment
else:
row['establishment_name'] = establishment_name
row_city = row.get('product_city')
if row_city:
city = row_city
else:
row['city_name'] = city
row_wine_color = row.pop('wine_color_section_name')
if row_wine_color:
wine_color = row_wine_color
else:
row['wine_color'] = wine_color
if row.pop("node_name") == "WineNode":
product = ET.SubElement(products, 'product')
for key, value in row.items():
prop = ET.SubElement(product, 'prop')
prop.set('name', key)
prop.text = str(value)
# create a new XML file with the results
tree = ET.ElementTree(data)
with open(self.file_path, 'bw') as f:
tree.write(f)
self.success = True
elif self.guide.guide_type in [self.guide.ARTISAN, self.guide.RESTAURANT]:
# establishment
# create the file structure
data = ET.Element('data')
items = ET.SubElement(data, 'cities')
city = None
ad_number_of_pages = None
ad_right_pages = None
for row in self.get_data():
row_advertorial = row.pop('advertorial')
if row_advertorial:
ad_number_of_pages = row_advertorial.get('number_of_pages')
ad_right_pages = row_advertorial.get('right_pages')
else:
row['ad_number_of_pages'] = ad_number_of_pages
row['ad_right_pages'] = ad_right_pages
row_city = row.get('city_name')
if row_city:
city = row_city
else:
row['city_name'] = city
if row.pop("node_name") == "EstablishmentNode":
item = ET.SubElement(items, 'city')
for key, value in row.items():
prop = ET.SubElement(item, 'prop')
prop.set('name', key)
prop.text = str(value)
# create a new XML file with the results
tree = ET.ElementTree(data)
with open(self.file_path, 'bw') as f:
tree.write(f)
self.success = True

View File

@ -30,7 +30,6 @@ MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION)
THUMBNAIL_DEBUG = True THUMBNAIL_DEBUG = True
# DATABASES # DATABASES
DATABASES = { DATABASES = {
'default': { 'default': {
@ -119,3 +118,6 @@ EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com' EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com'
EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt' EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt'
EMAIL_PORT = 587 EMAIL_PORT = 587
# ADD TRANSFER TO INSTALLED APPS
INSTALLED_APPS.append('transfer.apps.TransferConfig')

View File

@ -10,6 +10,7 @@ urlpatterns = [
path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')),
path('location/', include('location.urls.back')), path('location/', include('location.urls.back')),
path('news/', include('news.urls.back')), path('news/', include('news.urls.back')),
path('notifications/', include(('notification.urls.back', 'notification'), namespace='notification')),
path('review/', include('review.urls.back')), path('review/', include('review.urls.back')),
path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')),
path('products/', include(('product.urls.back', 'product'), namespace='product')), path('products/', include(('product.urls.back', 'product'), namespace='product')),