Merge branch 'develop' into features/migrate-wine

# Conflicts:
#	apps/product/models.py
#	apps/transfer/management/commands/transfer.py
#	apps/transfer/models.py
This commit is contained in:
Anatoly 2019-11-05 14:56:26 +03:00
commit d46ba31654
88 changed files with 1961 additions and 450 deletions

View File

@ -113,7 +113,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
# Check OAuth2 response # Check OAuth2 response
if oauth2_status != status.HTTP_200_OK: if oauth2_status != status.HTTP_200_OK:
raise utils_exceptions.OAuth2Error() raise utils_exceptions.OAuth2Error(detail=body)
# Get authenticated user # Get authenticated user
user = User.objects.by_oauth2_access_token(token=body.get('access_token'))\ user = User.objects.by_oauth2_access_token(token=body.get('access_token'))\

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-10-25 16:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('booking', '0002_auto_20191003_1601'),
]
operations = [
migrations.AddField(
model_name='booking',
name='amount',
field=models.CharField(default=None, max_length=30, null=True, verbose_name='prepayment price'),
),
migrations.AddField(
model_name='booking',
name='stripe_key',
field=models.TextField(default=None, null=True, verbose_name='stripe service payment key'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-10-25 16:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('booking', '0003_auto_20191025_1613'),
]
operations = [
migrations.AddField(
model_name='booking',
name='stripe_token',
field=models.TextField(default=None, null=True, verbose_name='stripe service pre-payed booking token'),
),
]

View File

@ -24,6 +24,9 @@ class Booking(ProjectBaseMixin):
pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None) pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None)
booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True, booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True,
db_index=True, ) db_index=True, )
stripe_key = models.TextField(null=True, default=None, verbose_name=_('stripe service payment key'))
stripe_token = models.TextField(null=True, default=None, verbose_name=_('stripe service pre-payed booking token'))
amount = models.CharField(null=True, default=None, verbose_name=_('prepayment price'), max_length=30)
user = models.ForeignKey( user = models.ForeignKey(
'account.User', verbose_name=_('booking owner'), null=True, 'account.User', verbose_name=_('booking owner'), null=True,
related_name='bookings', related_name='bookings',

View File

@ -1,10 +1,13 @@
from abc import ABC, abstractmethod
import json import json
from abc import ABC, abstractmethod
import requests import requests
from django.conf import settings from django.conf import settings
from rest_framework import status from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, status
from rest_framework.response import Response
import booking.models.models as models import booking.models.models as models
from rest_framework import serializers
class AbstractBookingService(ABC): class AbstractBookingService(ABC):
@ -24,8 +27,14 @@ class AbstractBookingService(ABC):
self.url = settings.LASTABLE_SERVICE self.url = settings.LASTABLE_SERVICE
@staticmethod @staticmethod
def get_certain_keys(d: dict, keys_to_preserve: set) -> dict: def get_certain_keys(d: dict, keys_to_preserve: set, required: set = {}, check: bool = True) -> dict:
""" Helper """ """ Helper """
if len(required) == 0 and check:
required = keys_to_preserve.copy()
if required and check:
diff = required - d.keys()
if diff:
raise serializers.ValidationError({field: _(f'This field is required') for field in diff})
return {key: d[key] for key in d.keys() & keys_to_preserve} return {key: d[key] for key in d.keys() & keys_to_preserve}
@abstractmethod @abstractmethod
@ -66,22 +75,22 @@ class GuestonlineService(AbstractBookingService):
def get_common_headers(self): def get_common_headers(self):
return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'} return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'}
def check_whether_booking_available(self, restaurant_id, date: str): def check_whether_booking_available(self, restaurant_id, *args, **kwargs):
super().check_whether_booking_available(restaurant_id, date) url = f'{self.url}v1/periods'
url = f'{self.url}v1/runtime_services' params = {'restaurant_id': restaurant_id, **kwargs}
params = {'restaurant_id': restaurant_id, 'date': date, 'expands[]': 'table_availabilities'}
r = requests.get(url, headers=self.get_common_headers(), params=params) r = requests.get(url, headers=self.get_common_headers(), params=params)
if not status.is_success(r.status_code): if not status.is_success(r.status_code):
return False return False
response = json.loads(r.content)['runtime_services'] self.response = r.json()
keys_to_preserve = {'left_seats', 'table_availabilities', 'closed', 'start_time', 'end_time', 'last_booking'}
response = map(lambda x: self.get_certain_keys(x, keys_to_preserve), response)
self.response = response
return True return True
def commit_booking(self, payload): def commit_booking(self, payload, stripe_token = None):
url = f'{self.url}v1/pending_bookings/{payload}/commit' url = f'{self.url}v1/pending_bookings/{payload}/commit'
r = requests.put(url, headers=self.get_common_headers()) if stripe_token:
r = requests.put(url, headers=self.get_common_headers(),
data=json.dumps({'stripe_token': stripe_token}))
else:
r = requests.put(url, headers=self.get_common_headers())
self.response = json.loads(r.content) self.response = json.loads(r.content)
if status.is_success(r.status_code) and self.response is None: if status.is_success(r.status_code) and self.response is None:
raise serializers.ValidationError(detail='Booking already committed.') raise serializers.ValidationError(detail='Booking already committed.')
@ -93,8 +102,13 @@ class GuestonlineService(AbstractBookingService):
payload['lastname'] = payload.pop('last_name') payload['lastname'] = payload.pop('last_name')
payload['firstname'] = payload.pop('first_name') payload['firstname'] = payload.pop('first_name')
payload['mobile_phone'] = payload.pop('phone') payload['mobile_phone'] = payload.pop('phone')
payload['user_locale'] = payload.pop('country_code')
headers = self.get_common_headers() headers = self.get_common_headers()
r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload})) r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload}))
response = r.json()
self.response = response.get('prepayment')
if not status.is_success(r.status_code):
return Response(status=r.status_code, data=response)
return status.is_success(r.status_code) return status.is_success(r.status_code)
def create_booking(self, payload): def create_booking(self, payload):
@ -103,7 +117,10 @@ class GuestonlineService(AbstractBookingService):
payload['persons'] = payload.pop('booked_persons_number') payload['persons'] = payload.pop('booked_persons_number')
payload['date'] = payload.pop('booking_date') payload['date'] = payload.pop('booking_date')
r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload)) r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload))
return json.loads(r.content)['id'] if status.is_success(r.status_code) else False if status.is_success(r.status_code):
return json.loads(r.content)['id']
else:
return Response(status=r.status_code, data=r.json())
def cancel_booking(self, payload): def cancel_booking(self, payload):
url = f'{self.url}v1/pending_bookings/{payload}' url = f'{self.url}v1/pending_bookings/{payload}'

View File

@ -42,13 +42,29 @@ class PendingBookingSerializer(serializers.ModelSerializer):
'user', 'user',
) )
class CommitBookingSerializer(serializers.ModelSerializer):
class Meta:
model = models.Booking
fields = (
'stripe_token',
)
def update(self, instance, validated_data):
"""Override update method"""
# Update user password from instance
service = instance.get_service_by_type(instance.type)
service.commit_booking(instance.pending_booking_id, validated_data.get('stripe_token'))
instance.stripe_token = validated_data.get('stripe_token')
instance.save()
return instance
class UpdateBookingSerializer(serializers.ModelSerializer): class UpdateBookingSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField() id = serializers.ReadOnlyField()
class Meta: class Meta:
model = models.Booking model = models.Booking
fields = ('booking_id', 'id') fields = ('booking_id', 'id', 'stripe_key', 'amount')
class GetBookingSerializer(serializers.ModelSerializer): class GetBookingSerializer(serializers.ModelSerializer):

View File

@ -8,6 +8,7 @@ urlpatterns = [
path('<int:establishment_id>/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'), path('<int:establishment_id>/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'),
path('<int:establishment_id>/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'), path('<int:establishment_id>/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'),
path('<int:pk>/', views.UpdatePendingBooking.as_view(), name='update-pending-booking'), path('<int:pk>/', views.UpdatePendingBooking.as_view(), name='update-pending-booking'),
path('<int:pk>/commit/', views.CommitPendingBooking.as_view(), name='update-pending-booking'),
path('<int:pk>/cancel/', views.CancelBooking.as_view(), name='cancel-existing-booking'), path('<int:pk>/cancel/', views.CancelBooking.as_view(), name='cancel-existing-booking'),
path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'), path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'),
path('retrieve/<int:pk>/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'), path('retrieve/<int:pk>/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'),

View File

@ -1,11 +1,13 @@
from rest_framework import generics, permissions, status, serializers
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from establishment.models import Establishment from rest_framework import generics, permissions, status, serializers
from booking.models.models import Booking, GuestonlineService, LastableService
from rest_framework.response import Response from rest_framework.response import Response
from booking.serializers.web import (PendingBookingSerializer,
UpdateBookingSerializer, GetBookingSerializer, CheckBookingSerializer) from booking.models.models import Booking, GuestonlineService, LastableService
from booking.serializers.web import (PendingBookingSerializer, UpdateBookingSerializer, GetBookingSerializer,
CheckBookingSerializer, CommitBookingSerializer)
from establishment.models import Establishment
from notification.models import Subscriber
from utils.methods import get_user_ip
class CheckWhetherBookingAvailable(generics.GenericAPIView): class CheckWhetherBookingAvailable(generics.GenericAPIView):
@ -28,7 +30,9 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView):
service = l_service service = l_service
service.service_id = establishment.lastable_id service.service_id = establishment.lastable_id
elif (not establishment.guestonline_id is None) and g_service \ elif (not establishment.guestonline_id is None) and g_service \
.check_whether_booking_available(establishment.guestonline_id, date): .check_whether_booking_available(establishment.guestonline_id,
**g_service.get_certain_keys(request.query_params,
{'date', 'persons'})):
is_booking_available = True is_booking_available = True
service = g_service service = g_service
service.service_id = establishment.guestonline_id service.service_id = establishment.guestonline_id
@ -61,7 +65,9 @@ class CreatePendingBooking(generics.CreateAPIView):
} }
data['pending_booking_id'] = service.create_booking( data['pending_booking_id'] = service.create_booking(
service.get_certain_keys(data.copy(), service_to_keys[data.get('type')])) service.get_certain_keys(data.copy(), service_to_keys[data.get('type')]))
if not data['pending_booking_id']: if isinstance(data['pending_booking_id'], Response):
return data['pending_booking_id']
elif not data['pending_booking_id']:
return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking') return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking')
data['booking_id'] = data['pending_booking_id'] if data.get('type') == Booking.LASTABLE else None data['booking_id'] = data['pending_booking_id'] if data.get('type') == Booking.LASTABLE else None
serializer = self.get_serializer(data=data) serializer = self.get_serializer(data=data)
@ -70,6 +76,13 @@ class CreatePendingBooking(generics.CreateAPIView):
return Response(status=status.HTTP_201_CREATED, data=serializer.data) return Response(status=status.HTTP_201_CREATED, data=serializer.data)
class CommitPendingBooking(generics.UpdateAPIView):
""" Commit pending booking """
queryset = Booking.objects.all()
permission_classes = (permissions.AllowAny,)
serializer_class = CommitBookingSerializer
class UpdatePendingBooking(generics.UpdateAPIView): class UpdatePendingBooking(generics.UpdateAPIView):
""" Update pending booking with contacts """ """ Update pending booking with contacts """
queryset = Booking.objects.all() queryset = Booking.objects.all()
@ -81,9 +94,31 @@ class UpdatePendingBooking(generics.UpdateAPIView):
data = request.data.copy() data = request.data.copy()
service = Booking.get_service_by_type(instance.type) service = Booking.get_service_by_type(instance.type)
data['pending_booking_id'] = instance.pending_booking_id data['pending_booking_id'] = instance.pending_booking_id
service.update_booking(service.get_certain_keys(data, { r = service.update_booking(service.get_certain_keys(data, {
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', 'note',
}, {
'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', 'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id',
})) }))
if isinstance(r, Response):
return r
if data.get('newsletter'):
Subscriber.objects.make_subscriber(email=data['email'], country_code=data['country_code'],
locale=request.locale, ip_address=get_user_ip(request),
user=None if request.user.is_anonymous else request.user)
if service.response:
# если есть предоплата, возвращаем фронту страйп-ключ для совершения оплаты и цену
amount = service.response.get('amount')
stripe_key = service.response.get('stripe_key')
data = {
'id': instance.pk,
'amount': amount,
'stripe_key': stripe_key,
'type': instance.type,
}
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
serializer.update(instance, data)
return Response(status=status.HTTP_200_OK, data=data)
service.commit_booking(data['pending_booking_id']) service.commit_booking(data['pending_booking_id'])
data = { data = {
'booking_id': service.response.get('id'), 'booking_id': service.response.get('id'),

View File

View File

@ -0,0 +1,169 @@
from django.core.management.base import BaseCommand
from establishment.models import Establishment
from location.models import Country, Language
from transfer.models import Collections
from collection.models import Collection
from news.models import News
class Command(BaseCommand):
help = 'Import collection'
def handle(self, *args, **kwargs):
raw_qs = Collections.objects.raw('''
select
distinct
a.id,
a.collection_id,
-- a.establishment_id,
a.title,
a.tag_name,
a.slug,
-- a.attachment_file_name,
-- a.attachment_content_type,
-- a.attachment_file_size,
a.attachment_suffix_url,
-- active as is_publish,
a.country_code,
-- a.geometries,
a.description,
min(a.start) AS start
from
(
select distinct
c.id,
c.id as collection_id,
m.establishment_id,
c.title, c.tag_name,
c.slug, c.attachment_file_name,
c.attachment_content_type, c.attachment_file_size,
c.attachment_suffix_url,
active,
s.country_code_2 as country_code,
c.geometries,
c.title as description,
m.created_at as start
from collections as c
join metadata m on m.value = c.tag_name
join establishments e on e.id = m.establishment_id
join sites s on s.id = c.site_id
where m.`key` = 'collection'
union
select distinct
c.id,
c.id as collection_id,
m.establishment_id,
c.title, c.tag_name,
c.slug, c.attachment_file_name,
c.attachment_content_type, c.attachment_file_size,
c.attachment_suffix_url,
active,
s.country_code_2 as country_code,
c.geometries,
c.title as description,
m.created_at as start
from collections as c
join metadata m on m.value = c.slug
join establishments e on e.id = m.establishment_id
join sites s on s.id = c.site_id
where m.`key` = 'collection'
) a
group by
a.id,
a.collection_id,
-- a.establishment_id,
a.title,
a.tag_name,
a.slug,
-- a.attachment_file_name,
-- a.attachment_content_type,
-- a.attachment_file_size,
a.attachment_suffix_url,
-- active as is_publish,
a.country_code,
a.description
''')
objects = []
queryset = [vars(query) for query in raw_qs]
for obj in queryset:
# establishment = Establishment.objects.filter(old_id=obj['establishment_id']).first()
# lang = Language.objects.filter(locale=obj['country_code'])
country = Country.objects.filter(code=obj['country_code']).first()
if country:
objects.append(
Collection(name={"en-GB": obj['title']}, collection_type=Collection.ORDINARY,
country=country,
description=obj['description'],
slug=obj['slug'], old_id=obj['collection_id'],
start=obj['start'],
image_url='https://s3.eu-central-1.amazonaws.com/gm-test.com/media/'+obj['attachment_suffix_url']
)
)
Collection.objects.bulk_create(objects)
raw_qs = Collections.objects.raw('''
select
distinct
a.id,
a.collection_id,
a.establishment_id,
a.active
from
(
select distinct
c.id,
c.id as collection_id,
m.establishment_id,
c.title, c.tag_name,
c.slug, c.attachment_file_name,
c.attachment_content_type, c.attachment_file_size,
c.attachment_suffix_url,
active,
s.country_code_2 as country_code,
c.geometries,
c.title as description,
m.created_at as start
from collections as c
join metadata m on m.value = c.tag_name
join establishments e on e.id = m.establishment_id
join sites s on s.id = c.site_id
where m.`key` = 'collection'
union
select distinct
c.id,
c.id as collection_id,
m.establishment_id,
c.title, c.tag_name,
c.slug, c.attachment_file_name,
c.attachment_content_type, c.attachment_file_size,
c.attachment_suffix_url,
active,
s.country_code_2 as country_code,
c.geometries,
c.title as description,
m.created_at as start
from collections as c
join metadata m on m.value = c.slug
join establishments e on e.id = m.establishment_id
join sites s on s.id = c.site_id
where m.`key` = 'collection'
) a
''')
queryset = [vars(query) for query in raw_qs]
for obj in queryset:
print('COLLECT_ID: {}'.format(obj['collection_id']))
est = Establishment.objects.filter(old_id=obj['establishment_id'])
if est.exists():
inst = est.first()
collect = Collection.objects.filter(old_id=obj['collection_id'])
collect.update(is_publish=obj['active'])
print(f'COLLECT COUNT {collect.count()}')
inst.collections.add(*list(collect))
# for c in collect:
# inst.collections.add(c)
inst.save()

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-10-31 13:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('collection', '0016_auto_20191024_1334'),
]
operations = [
migrations.AddField(
model_name='collection',
name='old_id',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@ -76,6 +76,7 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
slug = models.SlugField(max_length=50, unique=True, slug = models.SlugField(max_length=50, unique=True,
verbose_name=_('Collection slug'), editable=True, null=True) verbose_name=_('Collection slug'), editable=True, null=True)
old_id=models.IntegerField(null=True, blank=True)
objects = CollectionQuerySet.as_manager() objects = CollectionQuerySet.as_manager()
class Meta: class Meta:

View File

@ -1,112 +0,0 @@
import json
import pytz
from datetime import datetime
from http.cookies import SimpleCookie
from rest_framework import status
from rest_framework.test import APITestCase
from account.models import User
from collection.models import Collection, Guide
from establishment.models import Establishment, EstablishmentType
from location.models import Country
class BaseTestCase(APITestCase):
def setUp(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.newsletter = True
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'),
'country_code': 'en'})
class CollectionListTests(BaseTestCase):
def test_collection_list_Read(self):
response = self.client.get('/api/web/collections/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionDetailTests(BaseTestCase):
def setUp(self):
super().setUp()
# country = Country.objects.first()
# if not country:
country = Country.objects.create(
name=json.dumps({"en-GB": "Test country"}),
code="en"
)
country.save()
self.collection = Collection.objects.create(
name='Test collection',
is_publish=True,
start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc),
country=country,
slug='test-collection-slug',
)
self.collection.save()
def test_collection_detail_Read(self):
response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en',
format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionGuideTests(CollectionDetailTests):
def test_guide_list_Read(self):
response = self.client.get('/api/web/collections/guides/', format='json')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
class CollectionGuideDetailTests(CollectionDetailTests):
def setUp(self):
super().setUp()
self.guide = Guide.objects.create(
collection=self.collection,
start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc)
)
self.guide.save()
def test_guide_detail_Read(self):
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionWebHomeTests(CollectionDetailTests):
def setUp(self):
super().setUp()
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
for i in range(1, 5):
setattr(self, f"establishment{i}",
Establishment.objects.create(
name=f"Test establishment {i}",
establishment_type_id=self.establishment_type.id,
is_publish=True,
slug=f"test-establishment-{i}"
)
)
getattr(self, f"establishment{i}").collections.add(self.collection)
def test_collection_list_filter(self):
response = self.client.get('/api/web/collections/?country_code=en', format='json')
data = response.json()
self.assertIn('count', data)
self.assertGreater(data['count'], 0)

View File

@ -1,48 +0,0 @@
"""
Структура fields:
key - поле в таблице postgres
value - поле или группа полей в таблице legacy
В случае передачи группы полей каждое поле представляет собой кортеж, где:
field[0] - название аргумента
field[1] - название поля в таблице legacy
Опционально: field[2] - тип данных для преобразования
"""
card = {
"Collection": {
"data_type": "objects",
"dependencies": ("Country", ),
"fields": {
"Collections": {
# нету аналогов для полей description, start и end
"name": "title",
"slug": "slug",
"block_size": ("geometries", "django.db.models.JSONField"),
"is_publish": ("active", "django.db.models.BooleanField"),
"image_url": ("attachment_file_name", "django.db.models.URLField")
}
},
"relations": {
# "country": "Country",
}
},
"Guide": {
# как работать с ForeignKey на самого себя(self), поле "parent"
"data_type": "objects",
"dependencies": ("Collection", "self"),
"fields": {
"Guides": {
# нету аналогов для полей start и end
"name": "title"
}
},
"relations": {
# аналалог для поля "collection" не найдено
# "parent": "Guide",
# "collection": "Collection"
}
}
}
used_apps = ("location", )

View File

@ -57,9 +57,10 @@ class ProductInline(admin.TabularInline):
class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Establishment admin.""" """Establishment admin."""
list_display = ['id', '__str__', 'image_tag', ] list_display = ['id', '__str__', 'image_tag', ]
inlines = [
AwardInline, ContactPhoneInline, ContactEmailInline, # inlines = [
ReviewInline, CommentInline, ProductInline] # AwardInline, ContactPhoneInline, ContactEmailInline,
# ReviewInline, CommentInline, ProductInline]
raw_id_fields = ('address',) raw_id_fields = ('address',)
@ -78,6 +79,7 @@ class PlateInline(admin.TabularInline):
class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin): class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Menu admin.""" """Menu admin."""
list_display = ['id', 'category_translated'] list_display = ['id', 'category_translated']
raw_id_fields = ['establishment']
inlines = [ inlines = [
PlateInline, PlateInline,
] ]
@ -87,3 +89,8 @@ class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin):
return obj.category_translated return obj.category_translated
category_translated.short_description = _('category') category_translated.short_description = _('category')
@admin.register(models.RatingStrategy)
class RatingStrategyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for Rating Strategy model."""

View File

@ -0,0 +1,40 @@
from django.core.management.base import BaseCommand
import requests
from establishment.models import Establishment
from main.models import Currency
from location.models import Country
from django.template.defaultfilters import slugify
class Command(BaseCommand):
help = 'Add currency to new db'
def handle(self, *args, **kwargs):
count = 0
url = 'https://restcountries.eu/rest/v2/name/{country}'
countries = Country.objects.all()
for country in countries:
country_name = country.name["en-GB"]
resp = requests.get(url=url.format(country=country_name))
if resp.status_code == requests.codes.ok:
country_list = resp.json()
if isinstance(country_list, list):
currency_dict = country_list[0].get("currencies")[0]
if currency_dict:
name = currency_dict['name']
if name:
slug = slugify(name)
currency, _ = Currency.objects.get_or_create(
slug=slug,
)
currency.name = {"en-GB": name}
currency.sign = currency_dict["symbol"]
currency.save()
establishments = Establishment.objects.filter(address__city__country=country)
establishments.update(currency=currency)
count += 1
self.stdout.write(self.style.WARNING(f'Created/updated {count} objects.'))

View File

@ -1,31 +1,67 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from establishment.models import Establishment from establishment.models import Establishment
from transfer.models import Descriptions from transfer.models import Reviews, ReviewTexts
class Command(BaseCommand): class Command(BaseCommand):
help = 'Add description values from old db to new db' help = 'Add description values from old db reviews to new db'
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
count = 0 count = 0
update_locale = 0
valid_reviews = {}
queryset = Descriptions.objects.all() queryset = Reviews.objects.exclude(
for obj in queryset: establishment_id__isnull=True
).filter(
aasm_state='published'
).values_list(
'id',
'establishment_id',
'updated_at',
)
for r_id, establishment_id, new_date in queryset:
try: try:
establishment = Establishment.objects.get(old_id=obj.establishment.id) review_id, date = valid_reviews[establishment_id]
except Establishment.DoesNotExist: except KeyError:
continue valid_reviews[establishment_id] = (r_id, new_date)
except Establishment.MultipleObjectsReturned:
establishment = Establishment.objects.filter(old_id=obj.establishment.id).first()
else: else:
if new_date > date:
valid_reviews[establishment_id] = (r_id, new_date)
text_qs = ReviewTexts.objects.exclude(
locale__isnull=True
).filter(
review_id__in=(value[0] for value in valid_reviews.values()),
).values_list(
'review__establishment_id',
'locale',
'text',
)
for es_id, locale, text in text_qs:
establishment = Establishment.objects.filter(old_id=es_id).first()
if establishment:
description = establishment.description description = establishment.description
description.update({ description.update({
obj.locale: obj.text locale: text
}) })
establishment.description = description establishment.description = description
establishment.save() establishment.save()
count += 1 count += 1
break
# Если нет en-GB в поле
for establishment in Establishment.objects.filter(old_id__isnull=False):
description = establishment.description
if len(description) and 'en-GB' not in description:
description.update({
'en-GB': next(iter(description.values()))
})
establishment.description = description
establishment.save()
update_locale += 1
self.stdout.write(self.style.WARNING(f'Updated {count} objects.')) self.stdout.write(self.style.WARNING(f'Updated {count} objects.'))
self.stdout.write(self.style.WARNING(f'Updated en-GB locale - {count}'))

View File

@ -18,14 +18,16 @@ class Command(BaseCommand):
AND scope = 'public' AND scope = 'public'
AND type = 'Photo' AND type = 'Photo'
AND menu_id IS NULL AND menu_id IS NULL
GROUP BY establishment_id;''' GROUP BY establishment_assets.id, establishment_id, attachment_suffix_url;'''
) )
queryset = [vars(query) for query in raw_qs] queryset = [vars(query) for query in raw_qs]
for obj in queryset: for obj in queryset:
establishment = Establishment.objects.filter(old_id=obj['establishment_id']).first() establishment = Establishment.objects.filter(old_id=obj['establishment_id'], image_url__isnull=True).first()
if establishment: if establishment:
img = url + obj['attachment_suffix_url'] img = url + obj['attachment_suffix_url']
establishment.preview_image_url = img img_url = img[:-12]
extension = img.split('.')[-1:][0] # JPG
establishment.preview_image_url = f'{img_url}xlarge.{extension}'
establishment.image_url = img establishment.image_url = img
establishment.save() establishment.save()
count += 1 count += 1

View File

@ -0,0 +1,56 @@
from django.core.management.base import BaseCommand
from establishment.models import Establishment, SocialNetwork
from transfer.models import EstablishmentInfos
class Command(BaseCommand):
help = 'Add social links values from old db to new db'
def handle(self, *args, **kwargs):
count = 0
queryset = EstablishmentInfos.objects.exclude(
establishment_id__isnull=True
).values_list('id', 'establishment_id', 'facebook', 'twitter', 'instagram')
for id, es_id, facebook, twitter, instagram in queryset:
try:
establishment = Establishment.objects.get(old_id=es_id)
except Establishment.DoesNotExist:
continue
except Establishment.MultipleObjectsReturned:
establishment = Establishment.objects.filter(old_id=es_id).first()
else:
if facebook:
if 'facebook.com/' not in facebook:
facebook = 'https://www.facebook.com/' + facebook
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='facebook',
url=facebook,
)
count += 1
if twitter:
if 'twitter.com/' not in twitter:
twitter = 'https://www.twitter.com/' + twitter
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='twitter',
url=twitter,
)
count += 1
if instagram:
if 'instagram.com/' not in instagram:
instagram = 'https://www.instagram.com/' + instagram
obj, _ = SocialNetwork.objects.get_or_create(
old_id=id,
establishment=establishment,
title='instagram',
url=instagram,
)
count += 1
self.stdout.write(self.style.WARNING(f'Created/updated {count} objects.'))

View File

@ -0,0 +1,33 @@
from django.core.management.base import BaseCommand
from establishment.models import Establishment, Menu, Plate
from transfer.models import Menus
class Command(BaseCommand):
help = 'Add menus from old db to new db'
def handle(self, *args, **kwargs):
count = 0
menus = Menus.objects.filter(name__isnull=False).exclude(name='')
for old_menu in menus:
est = Establishment.objects.filter(
old_id=old_menu.establishment_id).first()
if est:
menu, _ = Menu.objects.get_or_create(
category={'en-GB': 'formulas'},
establishment=est
)
plate, created = Plate.objects.get_or_create(
name={"en-GB": old_menu.name},
menu=menu,
price=old_menu.price or 0
)
if created:
plate.currency_code = old_menu.currency
plate.currency = est.currency
plate.save()
count += 1
self.stdout.write(self.style.WARNING(f'Updated/created {count} objects.'))

View File

@ -0,0 +1,16 @@
"""Run recalculating toque number for establishments."""
from django.core.management.base import BaseCommand
from establishment.models import Establishment
class Command(BaseCommand):
help = 'Recalculation toque number for all establishments.'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def handle(self, *args, **options):
for establishment in Establishment.objects.select_related('address__city__country'):
print(f'Recalculate for {establishment.name} ({establishment.id})')
establishment.recalculate_toque_number()

View File

@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from establishment.models import Establishment
from transfer.models import Establishments as OldEst
class Command(BaseCommand):
help = 'Update transportation column in establishment'
def handle(self, *args, **kwargs):
count = 0
raw_qs = OldEst.objects.raw(
'''
select
e.id,
l.transportation
from establishments e
join locations l on l.id = e.location_id
where trim(l.transportation) is not null
and length(trim(l.transportation)) >1;
'''
)
queryset = [vars(query) for query in raw_qs]
for obj in queryset:
establishment = Establishment.objects.filter(old_id=obj['id']).first()
if establishment:
establishment.transportation = obj['transportation']
count += 1
establishment.save()
self.stdout.write(self.style.WARNING(f'Updated {count} objects.'))

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.4 on 2019-10-31 15:55
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('location', '0020_merge_20191030_1714'),
('establishment', '0047_merge_20191030_1714'),
]
operations = [
migrations.CreateModel(
name='RatingStrategy',
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')),
('toque_number', models.IntegerField(choices=[(1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four'), (5, 'Five')])),
('public_mark_min_value', models.IntegerField()),
('public_mark_max_value', models.IntegerField()),
('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country')),
],
options={
'verbose_name': 'Rating strategy',
'verbose_name_plural': 'Rating strategy',
'unique_together': {('country', 'toque_number')},
},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-10-31 16:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0048_ratingstrategy'),
]
operations = [
migrations.AlterField(
model_name='ratingstrategy',
name='country',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-01 07:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0049_auto_20191031_1616'),
]
operations = [
migrations.AddField(
model_name='socialnetwork',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-01 07:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0050_socialnetwork_old_id'),
]
operations = [
migrations.AlterField(
model_name='socialnetwork',
name='url',
field=models.URLField(max_length=255, verbose_name='URL'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-03 20:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0051_auto_20191101_0732'),
]
operations = [
migrations.AddField(
model_name='plate',
name='currency_code',
field=models.CharField(blank=True, default=None, max_length=250, null=True, verbose_name='currency code'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-11-03 20:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('establishment', '0052_plate_currency_code'),
]
operations = [
migrations.AlterField(
model_name='plate',
name='currency',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='main.Currency', verbose_name='currency'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-03 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0053_auto_20191103_2051'),
]
operations = [
migrations.AlterField(
model_name='plate',
name='is_signature_plate',
field=models.BooleanField(default=False, verbose_name='is signature plate'),
),
]

View File

@ -14,7 +14,6 @@ from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from pytz import timezone as ptz
from timezone_field import TimeZoneField from timezone_field import TimeZoneField
from collection.models import Collection from collection.models import Collection
@ -310,6 +309,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
public_mark = models.PositiveIntegerField(blank=True, null=True, public_mark = models.PositiveIntegerField(blank=True, null=True,
default=None, default=None,
verbose_name=_('public mark'),) verbose_name=_('public mark'),)
# todo: set default 0
toque_number = models.PositiveIntegerField(blank=True, null=True, toque_number = models.PositiveIntegerField(blank=True, null=True,
default=None, default=None,
verbose_name=_('toque number'),) verbose_name=_('toque number'),)
@ -384,7 +384,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
# todo: recalculate toque_number # todo: recalculate toque_number
def recalculate_toque_number(self): def recalculate_toque_number(self):
self.toque_number = 4 toque_number = 0
if self.address and self.public_mark:
toque_number = RatingStrategy.objects. \
get_toque_number(country=self.address.city.country,
public_mark=self.public_mark)
self.toque_number = toque_number
self.save()
def recalculate_price_level(self, low_price=None, high_price=None): def recalculate_price_level(self, low_price=None, high_price=None):
if low_price is None or high_price is None: if low_price is None or high_price is None:
@ -428,6 +434,27 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
def best_price_carte(self): def best_price_carte(self):
return 200 return 200
@property
def range_price_menu(self):
plates = self.menu_set.filter(
models.Q(category={'en-GB': 'formulas'})
).aggregate(
max=models.Max('plate__price', output_field=models.FloatField()),
min=models.Min('plate__price', output_field=models.FloatField()))
return plates
@property
def range_price_carte(self):
plates = self.menu_set.filter(
models.Q(category={'en-GB': 'starter'}) |
models.Q(category={'en-GB': 'main_course'}) |
models.Q(category={'en-GB': 'dessert'})
).aggregate(
max=models.Max('plate__price', output_field=models.FloatField()),
min=models.Min('plate__price', output_field=models.FloatField()),
)
return plates
@property @property
def works_noon(self): def works_noon(self):
""" Used for indexing working by day """ """ Used for indexing working by day """
@ -622,9 +649,12 @@ class Plate(TranslatedFieldsMixin, models.Model):
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')
price = models.DecimalField( price = models.DecimalField(
_('price'), max_digits=14, decimal_places=2) _('price'), max_digits=14, decimal_places=2)
is_signature_plate = models.BooleanField(_('is signature plate')) is_signature_plate = models.BooleanField(_('is signature plate'), default=False)
currency = models.ForeignKey( currency = models.ForeignKey(
'main.Currency', verbose_name=_('currency'), on_delete=models.CASCADE) 'main.Currency', verbose_name=_('currency'), on_delete=models.CASCADE,
blank=True, null=True, default=None)
currency_code = models.CharField(
_('currency code'), max_length=250, blank=True, null=True, default=None)
menu = models.ForeignKey( menu = models.ForeignKey(
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE) 'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
@ -656,11 +686,12 @@ class Menu(TranslatedFieldsMixin, BaseAttributes):
class SocialNetwork(models.Model): class SocialNetwork(models.Model):
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
establishment = models.ForeignKey( establishment = models.ForeignKey(
'Establishment', verbose_name=_('establishment'), 'Establishment', verbose_name=_('establishment'),
related_name='socials', on_delete=models.CASCADE) related_name='socials', on_delete=models.CASCADE)
title = models.CharField(_('title'), max_length=255) title = models.CharField(_('title'), max_length=255)
url = models.URLField(_('URL')) url = models.URLField(_('URL'), max_length=255)
class Meta: class Meta:
verbose_name = _('social network') verbose_name = _('social network')
@ -669,3 +700,62 @@ class SocialNetwork(models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
class RatingStrategyManager(models.Manager):
"""Extended manager for RatingStrategy."""
def get_toque_number(self, country, public_mark):
"""Get toque number for country and public_mark."""
qs = self.model.objects.by_country(country)
if not qs.exists():
qs = self.model.objects.by_country(None)
obj = qs.for_public_mark(public_mark).first()
if obj:
return obj.toque_number
return 0
class RatingStrategyQuerySet(models.QuerySet):
"""Extended queryset for RatingStrategy."""
def by_country(self, country):
"""Filter by country."""
return self.filter(country=country)
def for_public_mark(self, public_mark):
"""Filter for value."""
return self.filter(public_mark_min_value__lte=public_mark,
public_mark_max_value__gte=public_mark)
class RatingStrategy(ProjectBaseMixin):
"""Rating Strategy model."""
TOQUE_NUMBER_CHOICES = (
(1, _('One')),
(2, _('Two')),
(3, _('Three')),
(4, _('Four')),
(5, _('Five')),
)
country = models.ForeignKey('location.Country', null=True, blank=True,
default=None, on_delete=models.CASCADE,
verbose_name=_('Country'))
toque_number = models.IntegerField(choices=TOQUE_NUMBER_CHOICES)
public_mark_min_value = models.IntegerField()
public_mark_max_value = models.IntegerField()
objects = RatingStrategyManager.from_queryset(RatingStrategyQuerySet)()
class Meta:
"""Meta class."""
verbose_name = _('Rating strategy')
verbose_name_plural = _('Rating strategy')
unique_together = ('country', 'toque_number')
def __str__(self):
return f'{self.country.code if self.country else "Other country"}. ' \
f'"{self.toque_number}": {self.public_mark_min_value}-' \
f'{self.public_mark_max_value}'

View File

@ -149,6 +149,7 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer):
'establishment_type': {'write_only': True} 'establishment_type': {'write_only': True}
} }
class EstablishmentSubTypeGeoSerializer(EstablishmentSubTypeBaseSerializer): class EstablishmentSubTypeGeoSerializer(EstablishmentSubTypeBaseSerializer):
"""Serializer for EstablishmentSuType model w/ index_name.""" """Serializer for EstablishmentSuType model w/ index_name."""
@ -223,6 +224,11 @@ class EstablishmentGeoSerializer(EstablishmentBaseSerializer):
] ]
class RangePriceSerializer(serializers.Serializer):
max = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, default=0)
min = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, default=0)
class EstablishmentDetailSerializer(EstablishmentBaseSerializer): class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
"""Serializer for Establishment model.""" """Serializer for Establishment model."""
@ -240,6 +246,8 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
menu = MenuSerializers(source='menu_set', many=True, read_only=True) menu = MenuSerializers(source='menu_set', many=True, read_only=True)
best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
range_price_menu = RangePriceSerializer(read_only=True)
range_price_carte = RangePriceSerializer(read_only=True)
vintage_year = serializers.ReadOnlyField() vintage_year = serializers.ReadOnlyField()
class Meta(EstablishmentBaseSerializer.Meta): class Meta(EstablishmentBaseSerializer.Meta):
@ -264,6 +272,8 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer):
'menu', 'menu',
'best_price_menu', 'best_price_menu',
'best_price_carte', 'best_price_carte',
'range_price_menu',
'range_price_carte',
'transportation', 'transportation',
'vintage_year', 'vintage_year',
] ]

View File

@ -1,22 +1,24 @@
from pprint import pprint from pprint import pprint
import requests
from django.db.models import Q, F from django.db.models import Q, F
from transfer.models import Establishments, Dishes
from transfer.serializers.establishment import EstablishmentSerializer
from establishment.models import Establishment from establishment.models import Establishment
from location.models import Address from location.models import Address
from transfer.models import Establishments, Dishes
from transfer.serializers.establishment import EstablishmentSerializer
from transfer.serializers.plate import PlateSerializer from transfer.serializers.plate import PlateSerializer
def transfer_establishment(): def transfer_establishment():
result = [] result = []
old_establishments = Establishments.objects.filter( # todo: filter(location__city__name__icontains='paris')
location__city__name__icontains='paris', old_establishments = Establishments.objects.exclude(
id__in=list(Establishment.objects.all().values_list('old_id', flat=True))
).exclude( ).exclude(
Q(type='Wineyard') | Q(type='Wineyard') |
Q(location__timezone__isnull=True) Q(location__timezone__isnull=True),
).prefetch_related( ).prefetch_related(
'establishmentinfos_set', 'establishmentinfos_set',
'schedules_set', 'schedules_set',
@ -133,6 +135,5 @@ data_types = {
"location_establishment": [ "location_establishment": [
transfer_establishment_addresses transfer_establishment_addresses
], ],
"menu": [transfer_menu], "menu": [transfer_menu],
} }

View File

@ -7,7 +7,8 @@ from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from translation.models import Language from translation.models import Language
from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
TranslatedFieldsMixin, get_current_locale)
class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
@ -15,6 +16,13 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
STR_FIELD_NAME = 'name' STR_FIELD_NAME = 'name'
TWELVE_HOURS_FORMAT_COUNTRIES = [
'ca', # Canada
'au', # Australia
'us', # USA
'nz', # New Zealand
]
name = TJSONField(null=True, blank=True, default=None, name = TJSONField(null=True, blank=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}') verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
code = models.CharField(max_length=255, unique=True, verbose_name=_('Code')) code = models.CharField(max_length=255, unique=True, verbose_name=_('Code'))
@ -23,6 +31,12 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
languages = models.ManyToManyField(Language, verbose_name=_('Languages')) languages = models.ManyToManyField(Language, verbose_name=_('Languages'))
old_id = models.IntegerField(null=True, blank=True, default=None) old_id = models.IntegerField(null=True, blank=True, default=None)
@property
def time_format(self):
if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES:
return 'HH:mm'
return 'hh:mmA'
@property @property
def country_id(self): def country_id(self):
return self.id return self.id
@ -33,6 +47,14 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin):
verbose_name_plural = _('Countries') verbose_name_plural = _('Countries')
verbose_name = _('Country') verbose_name = _('Country')
def __str__(self):
str_name = self.code
if isinstance(self.name, dict):
translated_name = self.name.get(get_current_locale())
if translated_name:
str_name = translated_name
return str_name
class Region(models.Model): class Region(models.Model):
"""Region model.""" """Region model."""

View File

@ -1,16 +1,20 @@
from django.db.models import Q, QuerySet from transfer.serializers.location import CountrySerializer, RegionSerializer, \
CitySerializer, AddressSerializer, \
from transfer.serializers.location import CountrySerializer, RegionSerializer, CitySerializer, AddressSerializer Country
from transfer.models import Cities, Locations from transfer.models import Cities, Locations
from pprint import pprint from pprint import pprint
from requests import get
def transfer_countries(): def transfer_countries():
queryset = Cities.objects.raw("""SELECT cities.id, cities.country_code_2 queryset = Cities.objects.raw("""
FROM cities WHERE SELECT cities.id, cities.country_code_2
country_code_2 IS NOT NULL AND FROM cities
country_code_2 != "" WHERE country_code_2 IS NOT NULL AND
GROUP BY cities.country_code_2""") country_code_2 != ""
GROUP BY cities.id, cities.country_code_2
""")
queryset = [vars(query) for query in queryset] queryset = [vars(query) for query in queryset]
@ -22,16 +26,24 @@ def transfer_countries():
def transfer_regions(): def transfer_regions():
regions_without_subregion_queryset = Cities.objects.raw("""SELECT cities.id, cities.region_code, regions_without_subregion_queryset = Cities.objects.raw("""
cities.country_code_2, cities.subregion_code SELECT cities.id,
FROM cities WHERE cities.region_code,
(subregion_code IS NULL OR cities.country_code_2,
subregion_code = "") AND cities.subregion_code
region_code IS NOT NULL AND FROM cities
region_code != "" AND WHERE (subregion_code IS NULL
country_code_2 IS NOT NULL AND OR subregion_code = ""
country_code_2 != "" )
GROUP BY region_code""") AND region_code IS NOT NULL
AND region_code != ""
AND country_code_2 IS NOT NULL
AND country_code_2 != ""
GROUP BY cities.id,
cities.region_code,
cities.country_code_2,
cities.subregion_code
""")
regions_without_subregion_queryset = [vars(query) for query in regions_without_subregion_queryset] regions_without_subregion_queryset = [vars(query) for query in regions_without_subregion_queryset]
@ -41,25 +53,34 @@ def transfer_regions():
else: else:
pprint(f"Parent regions serializer errors: {serialized_without_subregion.errors}") pprint(f"Parent regions serializer errors: {serialized_without_subregion.errors}")
regions_with_subregion_queryset = Cities.objects.raw("""SELECT cities.id, cities.region_code, regions_with_subregion_queryset = Cities.objects.raw("""
cities.country_code_2, cities.subregion_code SELECT
FROM cities WHERE cities.id,
subregion_code IS NOT NULL AND cities.region_code,
subregion_code != "" AND cities.country_code_2,
region_code IS NOT NULL AND cities.subregion_code
region_code != "" AND FROM cities
country_code_2 IS NOT NULL AND WHERE subregion_code IS NOT NULL AND
country_code_2 != "" subregion_code != "" AND
AND cities.subregion_code in ( region_code IS NOT NULL AND
SELECT region_code FROM cities WHERE region_code != "" AND
(subregion_code IS NULL OR country_code_2 IS NOT NULL AND
subregion_code = "") AND country_code_2 != ""
region_code IS NOT NULL AND AND cities.subregion_code in
region_code != "" AND (
country_code_2 IS NOT NULL AND SELECT region_code FROM cities WHERE
country_code_2 != "" (subregion_code IS NULL OR
) subregion_code = "") AND
GROUP BY region_code""") region_code IS NOT NULL AND
region_code != "" AND
country_code_2 IS NOT NULL AND
country_code_2 != ""
)
GROUP BY cities.id,
cities.region_code,
cities.country_code_2,
cities.subregion_code
""")
regions_with_subregion_queryset = [vars(query) for query in regions_with_subregion_queryset] regions_with_subregion_queryset = [vars(query) for query in regions_with_subregion_queryset]
@ -112,6 +133,17 @@ def transfer_addresses():
pprint(f"Address serializer errors: {serialized_data.errors}") pprint(f"Address serializer errors: {serialized_data.errors}")
def update_flags():
queryset = Country.objects.only("id", "code", "svg_image").filter(old_id__isnull=False)
link_to_request = "https://s3.eu-central-1.amazonaws.com/gm-test.com/media"
for query in queryset:
svg_link = f"/svg/country/10-31-2019/{query.code}.svg"
resp = get(f"{link_to_request}{svg_link}")
if resp.status_code == 200:
query.svg_image = svg_link
query.save()
data_types = { data_types = {
"dictionaries": [ "dictionaries": [
@ -119,5 +151,8 @@ data_types = {
transfer_regions, transfer_regions,
transfer_cities, transfer_cities,
transfer_addresses transfer_addresses
],
"update_country_flag": [
update_flags
] ]
} }

View File

@ -26,8 +26,10 @@ class AwardAdmin(admin.ModelAdmin):
@admin.register(models.Currency) @admin.register(models.Currency)
class CurrencContentAdmin(admin.ModelAdmin): class CurrencyContentAdmin(admin.ModelAdmin):
"""CurrencContent admin""" """Currency Content admin"""
list_display = ['id', 'slug', 'code']
list_display_links = ['slug']
@admin.register(models.Carousel) @admin.register(models.Carousel)

View File

@ -0,0 +1,48 @@
# Generated by Django 2.2.4 on 2019-10-31 14:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0023_merge_20191028_0725'),
]
operations = [
migrations.AddField(
model_name='carousel',
name='active',
field=models.BooleanField(default=False, verbose_name='old active'),
),
migrations.AddField(
model_name='carousel',
name='attachment_suffix_url',
field=models.TextField(blank=True, default=None, null=True, verbose_name='old attachment_suffix_url'),
),
migrations.AddField(
model_name='carousel',
name='description',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='old description'),
),
migrations.AddField(
model_name='carousel',
name='link',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='old link'),
),
migrations.AddField(
model_name='carousel',
name='link_title',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='old link_title'),
),
migrations.AddField(
model_name='carousel',
name='old_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'),
),
migrations.AddField(
model_name='carousel',
name='title',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='old title'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-10-31 15:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0024_auto_20191031_1439'),
]
operations = [
migrations.AddField(
model_name='carousel',
name='is_parse',
field=models.BooleanField(default=False, verbose_name='is parse'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.4 on 2019-11-01 05:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('main', '0025_carousel_is_parse'),
]
operations = [
migrations.AlterField(
model_name='carousel',
name='content_type',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='carousel',
name='object_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.4 on 2019-11-01 14:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('location', '0020_merge_20191030_1714'),
('main', '0026_auto_20191101_0500'),
]
operations = [
migrations.AddField(
model_name='carousel',
name='country',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='country'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-03 12:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0027_carousel_country'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='sign',
field=models.CharField(max_length=3, verbose_name='sign'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-03 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0028_auto_20191103_1237'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='sign',
field=models.CharField(max_length=5, verbose_name='sign'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-03 13:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0029_auto_20191103_1344'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='sign',
field=models.CharField(max_length=255, verbose_name='sign'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-11-03 20:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0030_auto_20191103_1350'),
]
operations = [
migrations.AddField(
model_name='currency',
name='code',
field=models.CharField(default=None, max_length=5, null=True, unique=True),
),
migrations.AlterField(
model_name='currency',
name='slug',
field=models.SlugField(max_length=5, unique=True),
),
]

View File

@ -5,6 +5,7 @@ from django.conf import settings
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.core.validators import EMPTY_VALUES
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -14,96 +15,8 @@ from configuration.models import TranslationSettings
from location.models import Country from location.models import Country
from main import methods from main import methods
from review.models import Review from review.models import Review
from utils.models import (ProjectBaseMixin, TJSONField, from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
TranslatedFieldsMixin, ImageMixin, TranslatedFieldsMixin, PlatformMixin)
PlatformMixin, URLImageMixin)
from utils.querysets import ContentTypeQuerySetMixin
#
#
# class Tag(models.Model):
# """Tag model."""
# name = models.CharField(max_length=250)
#
#
# class MediaAsset(models.Model):
# """Assets model."""
# PHOTO = 0
# VIDEO = 1
# VIDEO_URL = 2
# PDF = 3
# DOCUMENT = 4
#
# ASSET_TYPE_CHOICES = (
# (PHOTO, _('photo')),
# (VIDEO, _('video')),
# (VIDEO_URL, _('video url')),
# (PDF, _('pdf')),
# (DOCUMENT, _('document'))
# )
#
# AMAZON_S3 = 0
#
# STORAGE_TYPE_CHOICES = (
# (AMAZON_S3, _('amazon s3')),
# )
#
# PUBLIC = 0
# PRIVATE = 1
# ROLE = 2
#
# ACCESS_TYPE_CHOICES = (
# (PUBLIC, _('public')),
# (PRIVATE, _('private')),
# (ROLE, _('role')),
# )
#
# asset_type = models.PositiveSmallIntegerField(
# _('asset type'), choices=ASSET_TYPE_CHOICES, default=PHOTO
# )
# storage_type = models.PositiveSmallIntegerField(
# _('storage type'), choices=STORAGE_TYPE_CHOICES, default=AMAZON_S3
# )
# storage_unit = models.CharField(_('storage unit'), blank=True, default='')
# path = models.CharField(_('path'))
# access_type = models.PositiveSmallIntegerField(
# _('access type'), choices=ACCESS_TYPE_CHOICES, default=PUBLIC)
# asset_text = JSONField(_('asset_text'))
# size = models.IntegerField(_('size'))
# version = models.CharField(_('version'), max_length=250)
# text = JSONField(_('text'))
#
#
# class Contacts(models.Model):
# """Contacts model."""
# address = models.ForeignKey(
# 'location.Address', verbose_name=_('address'), on_delete=models.CASCADE
# )
#
#
# class Social(models.Model):
# """Social model."""
# WEBSITE = 0
# SOCIAL = 1
#
# SOCIAL_TYPE_CHOICES = (
# (WEBSITE, _('website')),
# (SOCIAL, _('social')),
# )
#
# social_type = models.PositiveSmallIntegerField(
# _('social type'), choices=SOCIAL_TYPE_CHOICES
# )
#
#
# class Note(models.Model):
# """Note model."""
#
#
# class Ad(models.Model):
# """Ad model."""
#
class Currency(TranslatedFieldsMixin, models.Model): class Currency(TranslatedFieldsMixin, models.Model):
@ -111,8 +24,9 @@ class Currency(TranslatedFieldsMixin, models.Model):
name = TJSONField( name = TJSONField(
_('name'), null=True, blank=True, _('name'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}') default=None, help_text='{"en-GB":"some text"}')
sign = models.CharField(_('sign'), max_length=1) sign = models.CharField(_('sign'), max_length=255)
slug = models.SlugField(max_length=255, unique=True) slug = models.SlugField(max_length=5, unique=True)
code = models.CharField(max_length=5, unique=True, null=True, default=None)
class Meta: class Meta:
verbose_name = _('currency') verbose_name = _('currency')
@ -130,7 +44,6 @@ class SiteSettingsQuerySet(models.QuerySet):
class SiteSettings(ProjectBaseMixin): class SiteSettings(ProjectBaseMixin):
subdomain = models.CharField(max_length=255, db_index=True, unique=True, subdomain = models.CharField(max_length=255, db_index=True, unique=True,
verbose_name=_('Subdomain')) verbose_name=_('Subdomain'))
country = models.OneToOneField(Country, on_delete=models.PROTECT, country = models.OneToOneField(Country, on_delete=models.PROTECT,
@ -279,13 +192,41 @@ class AwardType(models.Model):
class CarouselQuerySet(models.QuerySet): class CarouselQuerySet(models.QuerySet):
"""Carousel QuerySet.""" """Carousel QuerySet."""
def is_parsed(self):
"""Parsed carousel objects."""
return self.filter(is_parse=True)
def active(self):
"""Active carousel objects."""
return self.filter(active=True)
def by_country_code(self, code):
"""Filter collection by country code."""
return self.filter(country__code=code)
class Carousel(models.Model): class Carousel(models.Model):
"""Carousel model.""" """Carousel model."""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True, default=None)
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField(blank=True, null=True, default=None)
content_object = generic.GenericForeignKey('content_type', 'object_id') content_object = generic.GenericForeignKey('content_type', 'object_id')
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
title = models.CharField(_('old title'), max_length=255, blank=True, null=True, default=None)
link = models.CharField(_('old link'), max_length=255, blank=True, null=True, default=None)
attachment_suffix_url = models.TextField(_('old attachment_suffix_url'), blank=True, null=True, default=None)
description = models.CharField(_('old description'), max_length=255, blank=True, null=True, default=None)
link_title = models.CharField(_('old link_title'), max_length=255, blank=True, null=True, default=None)
country = models.ForeignKey(
Country,
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name=_('country')
)
active = models.BooleanField(_('old active'), default=False)
is_parse = models.BooleanField(_('is parse'), default=False)
objects = CarouselQuerySet.as_manager() objects = CarouselQuerySet.as_manager()
class Meta: class Meta:
@ -325,6 +266,8 @@ class Carousel(models.Model):
@property @property
def image_url(self): def image_url(self):
if self.attachment_suffix_url:
return f'https://s3.eu-central-1.amazonaws.com/gm-test.com/media/{self.attachment_suffix_url}'
if hasattr(self.content_object, 'image_url'): if hasattr(self.content_object, 'image_url'):
return self.content_object.image_url return self.content_object.image_url
@ -340,5 +283,12 @@ class Carousel(models.Model):
@property @property
def model_name(self): def model_name(self):
if hasattr(self.content_object, 'establishment_type'): from establishment.models import Establishment
return self.content_object.establishment_type.name_translated from news.models import News
if isinstance(self.content_object, Establishment):
return self.content_object.establishment_type.index_name
elif isinstance(self.content_object, News):
return self.content_type.model
elif self.link not in EMPTY_VALUES:
return 'external'
return None

View File

@ -64,6 +64,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
country_code = serializers.CharField(source='subdomain', read_only=True) country_code = serializers.CharField(source='subdomain', read_only=True)
country_name = serializers.CharField(source='country.name_translated', read_only=True) country_name = serializers.CharField(source='country.name_translated', read_only=True)
time_format = serializers.CharField(source='country.time_format', read_only=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -71,6 +72,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
model = models.SiteSettings model = models.SiteSettings
fields = ( fields = (
'country_code', 'country_code',
'time_format',
'subdomain', 'subdomain',
'pinterest_page_url', 'pinterest_page_url',
'twitter_page_url', 'twitter_page_url',
@ -81,7 +83,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer):
'ad_config', 'ad_config',
'published_features', 'published_features',
'currency', 'currency',
'country_name' 'country_name',
) )
@ -135,6 +137,7 @@ class AwardSerializer(AwardBaseSerializer):
class CarouselListSerializer(serializers.ModelSerializer): class CarouselListSerializer(serializers.ModelSerializer):
"""Serializer for retrieving list of carousel items.""" """Serializer for retrieving list of carousel items."""
model_name = serializers.CharField() model_name = serializers.CharField()
name = serializers.CharField() name = serializers.CharField()
toque_number = serializers.IntegerField() toque_number = serializers.IntegerField()
@ -158,6 +161,7 @@ class CarouselListSerializer(serializers.ModelSerializer):
'vintage_year', 'vintage_year',
'last_award', 'last_award',
'slug', 'slug',
'link',
] ]

View File

@ -71,7 +71,8 @@ card = {
"facebook_page_url": "facebook_page_url", "facebook_page_url": "facebook_page_url",
"instagram_page_url": "instagram_page_url", "instagram_page_url": "instagram_page_url",
"contact_email": "contact_email", "contact_email": "contact_email",
"config": ("config", "django.db.models.JSONField") #TODO в качесте ключа использовать country_code_2 из legacy - ? "config": ("config", "django.db.models.JSONField")
# TODO в качесте ключа использовать country_code_2 из legacy - ?
}, },
# "relations": { # "relations": {
# # Как работать c отношение OneToOneField # # Как работать c отношение OneToOneField
@ -133,7 +134,7 @@ card = {
"vintage_year": "year", "vintage_year": "year",
} }
}, },
#TODO вопрос с content_type # TODO вопрос с content_type
"relations": { "relations": {
"AwardTypes": [( "AwardTypes": [(
("award_type", None), ("award_type", None),
@ -143,4 +144,4 @@ card = {
} }
} }
used_apps = ("locations", ) used_apps = ("locations",)

View File

@ -0,0 +1,23 @@
from pprint import pprint
from django.db.models import F
from transfer.models import CarouselElements
from transfer.serializers.carousel import CarouselSerializer
def transfer_carousel():
queryset = CarouselElements.objects.all().annotate(
country=F('home_page__site__country_code_2'),
)
serialized_data = CarouselSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f'Carousel serializer errors: {serialized_data.errors}')
data_types = {
'whirligig': [transfer_carousel]
}

View File

@ -8,5 +8,5 @@ common_urlpatterns = [
path('awards/', AwardView.as_view(), name='awards_list'), path('awards/', AwardView.as_view(), name='awards_list'),
path('awards/<int:pk>/', AwardRetrieveView.as_view(), name='awards_retrieve'), path('awards/<int:pk>/', AwardRetrieveView.as_view(), name='awards_retrieve'),
path('carousel/', CarouselListView.as_view(), name='carousel-list'), path('carousel/', CarouselListView.as_view(), name='carousel-list'),
path('determine-location/', DetermineLocation.as_view(), name='determine-location') path('determine-location/', DetermineLocation.as_view(), name='determine-location')
] ]

View File

@ -61,11 +61,19 @@ class AwardRetrieveView(generics.RetrieveAPIView):
class CarouselListView(generics.ListAPIView): class CarouselListView(generics.ListAPIView):
"""Return list of carousel items.""" """Return list of carousel items."""
queryset = models.Carousel.objects.all()
queryset = models.Carousel.objects.is_parsed().active()
serializer_class = serializers.CarouselListSerializer serializer_class = serializers.CarouselListSerializer
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
pagination_class = None pagination_class = None
def get_queryset(self):
country_code = self.request.country_code
qs = models.Carousel.objects.is_parsed().active()
if country_code:
qs = qs.by_country_code(country_code)
return qs
class DetermineLocation(generics.GenericAPIView): class DetermineLocation(generics.GenericAPIView):
"""Determine user's location.""" """Determine user's location."""

View File

@ -0,0 +1,35 @@
from django.core.management.base import BaseCommand
from news.models import News, NewsType
from tag.models import Tag, TagCategory
from transfer.models import PageMetadata, Pages, PageTexts
class Command(BaseCommand):
help = 'Remove old news from new bd'
def handle(self, *args, **kwargs):
count = 0
news_type, _ = NewsType.objects.get_or_create(name='News')
tag_cat, _ = TagCategory.objects.get_or_create(index_name='category')
news_type.tag_categories.add(tag_cat)
news_type.save()
old_news_tag = PageMetadata.objects.filter(key='category', page__pagetexts__isnull=False)
for old_tag in old_news_tag:
old_id_list = old_tag.page.pagetexts_set.all().values_list('id', flat=True)
# Make Tag
new_tag, created = Tag.objects.get_or_create(category=tag_cat, value=old_tag.value)
if created:
new_tag.label = {'en-GB': new_tag.value}
new_tag.save()
for id in old_id_list:
if isinstance(id, int):
news = News.objects.filter(old_id=id).first()
if news:
news.tags.add(new_tag)
news.save()
count += 1
self.stdout.write(self.style.WARNING(f'Create or update {count} objects.'))

View File

@ -7,6 +7,7 @@ from rest_framework.reverse import reverse
from rating.models import Rating from rating.models import Rating
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin
from utils.querysets import TranslationQuerysetMixin
class NewsType(models.Model): class NewsType(models.Model):
@ -27,7 +28,7 @@ class NewsType(models.Model):
return self.name return self.name
class NewsQuerySet(models.QuerySet): class NewsQuerySet(TranslationQuerysetMixin):
"""QuerySet for model News""" """QuerySet for model News"""
def rating_value(self): def rating_value(self):

View File

@ -215,7 +215,7 @@ class NewsDetailWebSerializer(NewsDetailSerializer):
'same_theme', 'same_theme',
'should_read', 'should_read',
'agenda', 'agenda',
'banner' 'banner',
) )

View File

@ -21,8 +21,9 @@ class NewsMixinView:
from django.conf import settings from django.conf import settings
"""Override get_queryset method.""" """Override get_queryset method."""
qs = models.News.objects.published().with_base_related() \ qs = models.News.objects.published() \
.order_by('-is_highlighted', '-created') .with_base_related() \
.order_by('-is_highlighted', '-created')
country_code = self.request.country_code country_code = self.request.country_code
if country_code: if country_code:
@ -36,7 +37,7 @@ class NewsMixinView:
qs = qs.by_country_code(country_code) qs = qs.by_country_code(country_code)
else: else:
qs = models.News.objects.filter( qs = models.News.objects.filter(
id__in=settings.HARDCODED_INTERNATIONAL_NEWS_IDS) old_id__in=settings.HARDCODED_INTERNATIONAL_NEWS_IDS)
return qs return qs
# temp code # temp code

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-01 09:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('partner', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='partner',
name='image',
field=models.URLField(null=True, verbose_name='Partner image URL'),
),
]

View File

@ -4,9 +4,10 @@ from django.utils.translation import gettext_lazy as _
from utils.models import ImageMixin, ProjectBaseMixin from utils.models import ImageMixin, ProjectBaseMixin
class Partner(ProjectBaseMixin, ImageMixin): class Partner(ProjectBaseMixin):
"""Partner model.""" """Partner model."""
url = models.URLField(verbose_name=_('Partner URL')) url = models.URLField(verbose_name=_('Partner URL'))
image = models.URLField(verbose_name=_('Partner image URL'), null=True)
class Meta: class Meta:
verbose_name = _('partner') verbose_name = _('partner')

View File

@ -7,6 +7,8 @@ from transfer.serializers.partner import PartnerSerializer
def transfer_partner(): def transfer_partner():
queryset = EstablishmentBacklinks.objects.filter(type="Partner") queryset = EstablishmentBacklinks.objects.filter(type="Partner")
# queryset = EstablishmentBacklinks.objects.all() # Partner and Sponsor
serialized_data = PartnerSerializer(data=list(queryset.values()), many=True) serialized_data = PartnerSerializer(data=list(queryset.values()), many=True)
if serialized_data.is_valid(): if serialized_data.is_valid():
serialized_data.save() serialized_data.save()

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.4 on 2019-10-30 13:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0002_product_slug'),
]
operations = [
migrations.AddField(
model_name='product',
name='old_id',
field=models.IntegerField(blank=True, null=True, verbose_name='Product old id'),
),
migrations.AddField(
model_name='product',
name='old_id_establishment',
field=models.IntegerField(blank=True, null=True, verbose_name='Establishment old id'),
),
migrations.AlterField(
model_name='product',
name='establishment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.4 on 2019-10-31 09:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0003_auto_20191030_1315'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='old_id_establishment',
),
migrations.AlterField(
model_name='product',
name='establishment',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment'),
),
migrations.AlterField(
model_name='product',
name='old_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='productsubtype',
name='index_name',
field=models.CharField(choices=[('rum', 'Rum'), ('other', 'Other'), ('extra brut', 'extra brut'), ('brut', 'brut'), ('brut nature', 'brut nature'), ('demi-sec', 'demi-sec'), ('Extra Dry', 'Extra Dry'), ('dosage zero', 'dosage zero'), ('sec', 'sec'), ('doux', 'doux'), ('moelleux', 'moelleux')], db_index=True, max_length=50, unique=True, verbose_name='Index name'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-10-31 09:29
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0004_auto_20191031_0923'),
]
operations = [
migrations.AlterField(
model_name='product',
name='characteristics',
field=django.contrib.postgres.fields.jsonb.JSONField(null=True, verbose_name='Characteristics'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-10-31 09:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0005_auto_20191031_0929'),
]
operations = [
migrations.AlterField(
model_name='product',
name='establishment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment'),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.4 on 2019-10-31 14:08
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('product', '0006_auto_20191031_0930'),
]
operations = [
migrations.RemoveField(
model_name='product',
name='old_id',
),
migrations.AlterField(
model_name='product',
name='characteristics',
field=django.contrib.postgres.fields.jsonb.JSONField(verbose_name='Characteristics', null=True),
),
migrations.AlterField(
model_name='product',
name='establishment',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='establishment.Establishment', verbose_name='establishment'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.4 on 2019-10-31 14:10
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('product', '0007_auto_20191031_1408'),
]
operations = [
migrations.AlterField(
model_name='product',
name='characteristics',
field=django.contrib.postgres.fields.jsonb.JSONField(null=True, verbose_name='Characteristics'),
),
]

View File

@ -71,8 +71,10 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
] ]
search_fields = { search_fields = {
'name': {'fuzziness': 'auto:3,4'}, 'name': {'fuzziness': 'auto:3,4',
'name_translated': {'fuzziness': 'auto:3,4'}, 'boost': '2'},
'name_translated': {'fuzziness': 'auto:3,4',
'boost': '2'},
'description': {'fuzziness': 'auto'}, 'description': {'fuzziness': 'auto'},
} }
translated_search_fields = ( translated_search_fields = (

View File

@ -5,8 +5,13 @@ from .models import Tag, TagCategory
@admin.register(Tag) @admin.register(Tag)
class TagAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin):
"""Admin model for model Tag.""" """Admin model for model Tag."""
list_display = ['id', 'value', 'category']
list_display_links = ['id', 'value',]
list_filter = ['value']
@admin.register(TagCategory) @admin.register(TagCategory)
class TagCategoryAdmin(admin.ModelAdmin): class TagCategoryAdmin(admin.ModelAdmin):
"""Admin model for model TagCategory.""" """Admin model for model TagCategory."""
list_display = ['id', 'index_name', ]
list_display_links = ['id', 'index_name', ]

View File

@ -1,11 +1,10 @@
"""Tag app filters.""" """Tag app filters."""
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from establishment.models import EstablishmentType from establishment.models import EstablishmentType
from django.conf import settings
from tag import models from tag import models
class TagsBaseFilterSet(filters.FilterSet):
class TagCategoryFilterSet(filters.FilterSet):
"""TagCategory filterset."""
# Object type choices # Object type choices
NEWS = 'news' NEWS = 'news'
@ -19,6 +18,18 @@ class TagCategoryFilterSet(filters.FilterSet):
type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES,
method='filter_by_type') method='filter_by_type')
def filter_by_type(self, queryset, name, value):
if self.NEWS in value:
queryset = queryset.for_news()
if self.ESTABLISHMENT in value:
queryset = queryset.for_establishments()
return queryset
class TagCategoryFilterSet(TagsBaseFilterSet):
"""TagCategory filterset."""
establishment_type = filters.ChoiceFilter( establishment_type = filters.ChoiceFilter(
choices=EstablishmentType.INDEX_NAME_TYPES, choices=EstablishmentType.INDEX_NAME_TYPES,
method='by_establishment_type') method='by_establishment_type')
@ -30,13 +41,31 @@ class TagCategoryFilterSet(filters.FilterSet):
fields = ('type', fields = ('type',
'establishment_type', ) 'establishment_type', )
def filter_by_type(self, queryset, name, value):
if self.NEWS in value:
queryset = queryset.for_news()
if self.ESTABLISHMENT in value:
queryset = queryset.for_establishments()
return queryset
# todo: filter by establishment type # todo: filter by establishment type
def by_establishment_type(self, queryset, name, value): def by_establishment_type(self, queryset, name, value):
return queryset.by_establishment_type(value) return queryset.by_establishment_type(value)
class TagsFilterSet(TagsBaseFilterSet):
"""Chosen tags filterset."""
class Meta:
"""Meta class."""
model = models.Tag
fields = ('type',)
# TMP TODO remove it later
# Временный хардкод для демонстрации 4 ноября, потом удалить!
def filter_by_type(self, queryset, name, value):
""" Overrides base filter. Temporary decision"""
if not (settings.NEWS_CHOSEN_TAGS and settings.ESTABLISHMENT_CHOSEN_TAGS):
return super().filter_by_type(queryset, name, value)
queryset = models.Tag.objects
if self.NEWS in value:
queryset = queryset.for_news().filter(value__in=settings.NEWS_CHOSEN_TAGS).distinct('value')
if self.ESTABLISHMENT in value:
queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct(
'value')
return queryset

View File

@ -10,7 +10,9 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
existing_establishment = Establishment.objects.filter(old_id__isnull=False) existing_establishment = Establishment.objects.filter(
old_id__isnull=False, tags__isnull=True
)
ESTABLISHMENT = 1 ESTABLISHMENT = 1
SHOP = 2 SHOP = 2
RESTAURANT = 3 RESTAURANT = 3
@ -52,18 +54,35 @@ class Command(BaseCommand):
# create Tag # create Tag
for tag in key_value.metadata_set.filter( for tag in key_value.metadata_set.filter(
establishment__id__in=list(existing_establishment.values_list('old_id', flat=True))): establishment__id__in=list(
existing_establishment.values_list('old_id', flat=True)
)):
new_tag, _ = Tag.objects.get_or_create( new_tag, created = Tag.objects.get_or_create(
label={
'en-GB': tag.value,
'fr-FR': tag.value,
'ru-RU': tag.value,
},
value=tag.value, value=tag.value,
category=tag_category, category=tag_category,
) )
est = existing_establishment.get(old_id=tag.establishment_id) if created:
est.tags.add(new_tag)
est.save()
sp = tag.value.split('_')
value = ' '.join([sp[0].capitalize()] + sp[1:])
trans = {
'en-GB': value,
'fr-FR': value,
'ru-RU': value,
}
aliases = legacy.MetadatumAliases.objects.filter(value=tag.value)
for alias in aliases:
trans[alias.locale] = alias.meta_alias
new_tag.label = trans
new_tag.save()
est = existing_establishment.filter(
old_id=tag.establishment_id).first()
if est:
est.tags.add(new_tag)
est.save()

View File

@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from establishment.models import Establishment, EstablishmentType
from transfer import models as legacy
from tag.models import Tag, TagCategory
class Command(BaseCommand):
help = 'Add tags translation from old db to new db'
def handle(self, *args, **kwargs):
translation = legacy.MetadatumAliases.objects.all()
# Humanisation for default values
tags = Tag.objects.all()
for tag in tags:
value = tag.label
for k, v in value.items():
sp = v.split('_')
v = ' '.join([sp[0].capitalize()] + sp[1:])
tag.label[k] = v
tag.save()
for trans in translation:
tag = Tag.objects.filter(value=trans.value).first()
if tag:
tag.label.update(
{trans.locale: trans.meta_alias}
)
tag.save()

View File

@ -0,0 +1,37 @@
# Generated by Django 2.2.4 on 2019-11-01 12:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('location', '0020_merge_20191030_1714'),
('tag', '0007_auto_20191030_1514'),
]
operations = [
migrations.RemoveField(
model_name='tag',
name='priority',
),
migrations.CreateModel(
name='ChosenTagSettings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('priority', models.IntegerField(default=0)),
('country', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='location.Country')),
('tag', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tag.Tag')),
],
options={
'verbose_name': 'Tag',
'verbose_name_plural': 'Tags',
},
),
migrations.AddField(
model_name='tag',
name='chosen_tag_settings',
field=models.ManyToManyField(through='tag.ChosenTagSettings', to='location.Country'),
),
]

View File

@ -1,16 +1,26 @@
"""Tag app models.""" """Tag app models."""
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from configuration.models import TranslationSettings from configuration.models import TranslationSettings
from location.models import Country
from utils.models import TJSONField, TranslatedFieldsMixin from utils.models import TJSONField, TranslatedFieldsMixin
class TagQuerySet(models.QuerySet): class TagQuerySet(models.QuerySet):
def filter_chosen(self):
return self.exclude(priority__isnull=True) def for_news(self):
"""Select chosen tags for news."""
return self.filter(category__news_types__isnull=False)
def for_establishments(self):
"""Select chosen tags for establishments."""
return self.filter(models.Q(category__establishment_types__isnull=False) |
models.Q(category__establishment_subtypes__isnull=False))
def order_by_priority(self): def order_by_priority(self):
return self.order_by('priority') return self.order_by('chosentagsettings__priority')
class Tag(TranslatedFieldsMixin, models.Model): class Tag(TranslatedFieldsMixin, models.Model):
@ -24,7 +34,8 @@ class Tag(TranslatedFieldsMixin, models.Model):
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
null=True, related_name='tags', null=True, related_name='tags',
verbose_name=_('Category')) verbose_name=_('Category'))
priority = models.IntegerField(unique=True, null=True, default=None)
chosen_tag_settings = models.ManyToManyField(Country, through='ChosenTagSettings')
objects = TagQuerySet.as_manager() objects = TagQuerySet.as_manager()
@ -35,11 +46,29 @@ class Tag(TranslatedFieldsMixin, models.Model):
verbose_name_plural = _('Tags') verbose_name_plural = _('Tags')
def __str__(self): def __str__(self):
label = 'None' return f'Tag (id = {self.id}, label_translated = {self.label_translated})'
lang = TranslationSettings.get_solo().default_language
if self.label and lang in self.label:
label = self.label[lang] class ChosenTagSettingsQuerySet(models.QuerySet):
return f'id:{self.id}-{label}'
def by_country_code(self, country_code):
return self.filter(country__code=country_code)
class ChosenTagSettings(models.Model):
"""Tag model."""
tag = models.ForeignKey(Tag, on_delete=models.PROTECT)
country = models.ForeignKey(Country, on_delete=models.PROTECT)
priority = models.IntegerField(null=False, default=0)
objects = ChosenTagSettingsQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('Tag')
verbose_name_plural = _('Tags')
class TagCategoryQuerySet(models.QuerySet): class TagCategoryQuerySet(models.QuerySet):
@ -55,7 +84,7 @@ class TagCategoryQuerySet(models.QuerySet):
def for_news(self): def for_news(self):
"""Select tag categories for news.""" """Select tag categories for news."""
return self.filter(news_types__isnull=True) return self.filter(news_types__isnull=False)
def for_establishments(self): def for_establishments(self):
"""Select tag categories for establishments.""" """Select tag categories for establishments."""
@ -106,8 +135,4 @@ class TagCategory(TranslatedFieldsMixin, models.Model):
verbose_name_plural = _('Tag categories') verbose_name_plural = _('Tag categories')
def __str__(self): def __str__(self):
label = 'None' return self.index_name
lang = TranslationSettings.get_solo().default_language
if self.label and lang in self.label:
label = self.label[lang]
return f'id:{self.id}-{label}'

View File

@ -12,9 +12,9 @@ from utils.serializers import TranslatedField
class TagBaseSerializer(serializers.ModelSerializer): class TagBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Tag.""" """Serializer for model Tag."""
# todo: refactor this label_translated = TranslatedField()
# label_translated = TranslatedField() # label_translated = serializers.CharField(source='value', read_only=True, allow_null=True)
label_translated = serializers.CharField(source='value', read_only=True, allow_null=True) index_name = serializers.CharField(source='value', read_only=True, allow_null=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -23,6 +23,7 @@ class TagBaseSerializer(serializers.ModelSerializer):
fields = ( fields = (
'id', 'id',
'label_translated', 'label_translated',
'index_name',
) )

View File

@ -7,7 +7,7 @@ app_name = 'tag'
router = SimpleRouter() router = SimpleRouter()
router.register(r'categories', views.TagCategoryViewSet) router.register(r'categories', views.TagCategoryViewSet)
router.register(r'chosen_tags', views.ChosenTagsView, basename='Tag') router.register(r'chosen_tags', views.ChosenTagsView)
urlpatterns = [ urlpatterns = [

View File

@ -1,21 +1,45 @@
"""Tag views.""" """Tag views."""
from django.conf import settings
from rest_framework import permissions
from rest_framework import viewsets, mixins, status, generics from rest_framework import viewsets, mixins, status, generics
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from tag import filters, models, serializers from tag import filters, models, serializers
from rest_framework import permissions
class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet):
pagination_class = None pagination_class = None
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.TagBaseSerializer serializer_class = serializers.TagBaseSerializer
filterset_class = filters.TagsFilterSet
queryset = models.Tag.objects.all()
def get_queryset(self): def get_queryset(self):
return models.Tag.objects\ result_tags_ids = models.ChosenTagSettings.objects \
.filter_chosen() \ .by_country_code(self.request.country_code) \
.values('tag_id')
return models.Tag.objects \
.filter(id__in=result_tags_ids) \
.order_by_priority() .order_by_priority()
def list(self, request, *args, **kwargs):
# TMP TODO remove it later
# Временный хардкод для демонстрации 4 ноября, потом удалить!
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
result_list = serializer.data
if request.query_params.get('type') and (settings.ESTABLISHMENT_CHOSEN_TAGS or settings.NEWS_CHOSEN_TAGS):
ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get('type') == 'establishment' else settings.NEWS_CHOSEN_TAGS
result_list = sorted(result_list, key=lambda x: ordered_list.index(x['index_name']))
return Response(result_list)
# User`s views & viewsets # User`s views & viewsets
class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
@ -23,8 +47,8 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
filterset_class = filters.TagCategoryFilterSet filterset_class = filters.TagCategoryFilterSet
pagination_class = None pagination_class = None
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny,)
queryset = models.TagCategory.objects.with_tags().with_base_related().\ queryset = models.TagCategory.objects.with_tags().with_base_related(). \
distinct() distinct()
serializer_class = serializers.TagCategoryBaseSerializer serializer_class = serializers.TagCategoryBaseSerializer
@ -62,7 +86,7 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
"""List/create tag view.""" """List/create tag view."""
pagination_class = None pagination_class = None
permission_classes = (permissions.IsAuthenticated, ) permission_classes = (permissions.IsAuthenticated,)
queryset = models.Tag.objects.all() queryset = models.Tag.objects.all()
serializer_class = serializers.TagBackOfficeSerializer serializer_class = serializers.TagBackOfficeSerializer
bind_object_serializer_class = serializers.TagBindObjectSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer
@ -96,7 +120,7 @@ class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin,
TagCategoryViewSet): TagCategoryViewSet):
"""ViewSet for TagCategory model for BackOffice users.""" """ViewSet for TagCategory model for BackOffice users."""
permission_classes = (permissions.IsAuthenticated, ) permission_classes = (permissions.IsAuthenticated,)
queryset = TagCategoryViewSet.queryset.with_extended_related() queryset = TagCategoryViewSet.queryset.with_extended_related()
serializer_class = serializers.TagCategoryBackOfficeDetailSerializer serializer_class = serializers.TagCategoryBackOfficeDetailSerializer
bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer

View File

@ -8,7 +8,7 @@ class Command(BaseCommand):
"""Типы данных для трансфера """Типы данных для трансфера
ВНИМАНИЕ: первые буквы типов данных должны быть уникальны! ВНИМАНИЕ: первые буквы типов данных должны быть уникальны!
""" """
DATA_TYPES = [ SHORT_DATA_TYPES = [
'dictionaries', 'dictionaries',
'news', 'news',
'account', 'account',
@ -22,20 +22,39 @@ class Command(BaseCommand):
'tmp', 'tmp',
'menu', 'menu',
'location_establishment', 'location_establishment',
'wine_color', 'whirligig',
]
LONG_DATA_TYPES = [
'update_country_flag',
] ]
def handle(self, *args, **options): def handle(self, *args, **options):
"""Находим включённую опцию путём пересечения множества типов данных и множества включённых опций""" """Находим включённую опцию путём пересечения множества типов данных и множества включённых опций"""
data_type = list(set(option for option in options.keys() if options[option]) & set(self.DATA_TYPES)) data_type = list(set(option for option in options.keys() if options[option]) & set(self.LONG_DATA_TYPES))
if len(data_type) != 1: if len(data_type) != 1:
print("You must set correct option!\r\nYou can get options list with \r\n\r\n\tmanage.py help transfer\r\n") data_type = list(set(option for option in options.keys() if options[option]) & set(self.SHORT_DATA_TYPES))
exit(1) if len(data_type) != 1:
transfer_objects(data_type[0]) print("You must set correct option!\r\nYou can get options list with \r\n\r\n\tmanage.py help transfer\r\n")
exit(1)
else:
data_type = data_type[0]
else:
data_type = data_type[0]
transfer_objects(data_type)
def add_arguments(self, parser): def add_arguments(self, parser):
for option_type in self.LONG_DATA_TYPES:
parser.add_argument(
f'--{option_type}',
action='store_true',
default=False,
help=f'Transfer {option_type} objects'
)
"""Добавляем опции к команде, основываясь на типах данных, определённых в DATA_TYPES""" """Добавляем опции к команде, основываясь на типах данных, определённых в DATA_TYPES"""
for option_type in self.DATA_TYPES: for option_type in self.SHORT_DATA_TYPES:
parser.add_argument( parser.add_argument(
f'-{option_type[:1]}', f'-{option_type[:1]}',
f'--{option_type}', f'--{option_type}',

View File

@ -553,6 +553,7 @@ class EstablishmentInfos(MigrateMixin):
website = models.CharField(max_length=255, blank=True, null=True) website = models.CharField(max_length=255, blank=True, null=True)
facebook = models.CharField(max_length=255, blank=True, null=True) facebook = models.CharField(max_length=255, blank=True, null=True)
twitter = models.CharField(max_length=255, blank=True, null=True) twitter = models.CharField(max_length=255, blank=True, null=True)
instagram = models.TextField(blank=True, null=True)
lafourchette = models.CharField(max_length=255, blank=True, null=True) lafourchette = models.CharField(max_length=255, blank=True, null=True)
pub = models.IntegerField(blank=True, null=True) pub = models.IntegerField(blank=True, null=True)
created_at = models.DateTimeField() created_at = models.DateTimeField()
@ -967,3 +968,51 @@ class WineTypes(MigrateMixin):
# class Meta: # class Meta:
# managed = False # managed = False
# db_table = 'products' # db_table = 'products'
class HomePages(models.Model):
using = 'legacy'
site = models.ForeignKey(Sites, models.DO_NOTHING, blank=True, null=True)
selection_of_week = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'home_pages'
class CarouselElements(MigrateMixin):
using = 'legacy'
title = models.CharField(max_length=255, blank=True, null=True)
link = models.CharField(max_length=255, blank=True, null=True)
home_page = models.ForeignKey(HomePages, models.DO_NOTHING, blank=True, null=True)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
attachment_file_name = models.CharField(max_length=255, blank=True, null=True)
attachment_content_type = models.CharField(max_length=255, blank=True, null=True)
attachment_file_size = models.IntegerField(blank=True, null=True)
attachment_updated_at = models.DateTimeField(blank=True, null=True)
attachment_suffix_url = models.TextField(blank=True, null=True)
geometries = models.CharField(max_length=1024, blank=True, null=True)
active = models.IntegerField(blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
link_title = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'carousel_elements'
class MetadatumAliases(MigrateMixin):
"""MetadatumAliases model."""
using = 'legacy'
meta_alias = models.CharField(max_length=255, blank=True, null=True)
value = models.CharField(max_length=255, blank=True, null=True)
locale = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
class Meta:
managed = False
db_table = 'metadatum_aliases'

View File

@ -0,0 +1,68 @@
from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from establishment.models import Establishment
from location.models import Country
from main.models import Carousel
from news.models import News
def get_obj_data(model, slug):
try:
obj = model.objects.get(slug=slug)
except model.DoesNotExist:
return None, None
else:
content_type = ContentType.objects.get_for_model(obj)
return obj.id, content_type.id
class CarouselSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(allow_null=True, allow_blank=True)
link = serializers.CharField(allow_null=True)
link_title = serializers.CharField(allow_null=True, allow_blank=True)
description = serializers.CharField(allow_null=True, allow_blank=True)
attachment_suffix_url = serializers.CharField(allow_null=True)
active = serializers.IntegerField()
country = serializers.CharField(allow_null=True)
def create(self, validated_data):
object_id, content_type_id = self.get_content_type(validated_data)
validated_data.update({
'old_id': validated_data['id'],
'country': self.get_country(validated_data),
'active': bool(int(validated_data['active'])),
'content_type_id': content_type_id,
'object_id': object_id,
'is_parse': bool(object_id),
})
validated_data.pop('id')
obj, _ = Carousel.objects.update_or_create(**validated_data)
return obj
@staticmethod
def get_country(data):
return Country.objects.filter(code__iexact=data['country']).first()
@staticmethod
def get_content_type(data):
link = data['link']
if not link:
return None
obj_data = None, None
site = 'gaultmillau.com'
if site in link:
data = link.split('/')
try:
_type = data[3]
except IndexError:
pass
else:
if _type in ('news', 'pages'):
obj_data = get_obj_data(News, data[4])
elif _type == 'restaurant':
obj_data = get_obj_data(Establishment, data[4])
return obj_data

View File

@ -95,7 +95,6 @@ class EstablishmentSerializer(serializers.ModelSerializer):
if address: if address:
return address.id return address.id
return None return None
# return None
@staticmethod @staticmethod
def get_type(data): def get_type(data):

View File

@ -71,7 +71,7 @@ class RegionSerializer(serializers.ModelSerializer):
print(data) print(data)
if "subregion_code" in data and data["subregion_code"] is not None and data["subregion_code"].strip() != "": if "subregion_code" in data and data["subregion_code"] is not None and data["subregion_code"].strip() != "":
try: try:
parent_region = Region.objects.get(code=str(data['region_code'])) parent_region = Region.objects.filter(code=str(data['region_code'])).first()
except Exception as e: except Exception as e:
raise ValueError(f"Parent region error with {data}: {e}") raise ValueError(f"Parent region error with {data}: {e}")

View File

@ -4,12 +4,80 @@ from partner.models import Partner
class PartnerSerializer(serializers.ModelSerializer): class PartnerSerializer(serializers.ModelSerializer):
backlink_url = serializers.CharField(source="url") backlink_url = serializers.CharField(source="url")
partnership_icon = serializers.CharField()
partnership_name = serializers.CharField()
class Meta: class Meta:
model = Partner model = Partner
fields = ( fields = (
"backlink_url", "backlink_url",
"partnership_icon",
"partnership_name"
) )
def validate(self, data):
data["image"] = partnership_to_image_url.get(data["partnership_name"]).get(data["partnership_icon"])
data.pop("partnership_name")
data.pop("partnership_icon")
return data
def create(self, validated_data): def create(self, validated_data):
return Partner.objects.create(**validated_data) return Partner.objects.create(**validated_data)
partnership_to_image_url = {
# partnership_name
"tables-auberges": {
# partnership_icon
"Table Gastronomique":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/tables-auberges/Table Gastronomique-c1908231742bc0d0909c03cdac723c1daa3ee620134e8a6fbfae78efdaab8263.png",
"Bistrot Gourmand":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/tables-auberges/Bistrot Gourmand-fdf0527d8ed05d9c7d26456e531301325582f04e1f8e5d4c283ee4eac367dba1.png",
"Auberges de Village":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/tables-auberges/Auberges de Village-39aab1bbc678a6a5f7e0a2c9b8565bb6d21b01294371533580cdff0d9aa80da0.png",
"Table de Prestige":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/tables-auberges/Table de Prestige-6bf4ab0fcc56d89ffc97cc71553f0b1d21b92f4b5ef7656b832ab03e5c6ff31c.png",
"Table de Terroir":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/tables-auberges/Table de Terroir-e72a35a60c4a4575c162398d7355fc77787f368ff240ff8725dc24a37a497be7.png"
},
"riedel": {
"riedel":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/riedel/riedel-1fe0fa8298ae76d0a2de6e00316d2dd4357cace659cc0398629ae4c117bd114e.png"
},
"lavazza": {
"lavazza":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/lavazza/lavazza-9a013b9f954dd63ba871019a1fcd55ea5cb84b38ab2dbff47ee9464f671195bb.png"
},
"kaviari": {
"Kaviari Paris":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/kaviari/Kaviari Paris-e6c4fe81a1d5257854f9b8e28cb244b01a9129ed01056cb98e809f70ec7b5fcf.png"
},
"mutti": {
"mutti":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/mutti/mutti-86f8514b03e096b8e6843e8885756d311486c461a5b8c4f2f10f75995b146814.png"
},
"nespresso": {
"nespresso":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/nespresso/nespresso-c401e553731c68a364754fe512f25889398d42f98bf8edbe1233695b546d1c5d.png"
},
"le-manoir": {
"le-manoir":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/le-manoir/le-manoir-d6a616bebca2948c147ed7c58d9883682ee2d4a3779dea98e9d1a94f17e14771.png"
},
"discovery-cheque": {
"discovery-cheque":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/discovery-cheque/discovery-cheque-665a3c60d3d6302897863b90245f405979760a27bea06c98aa4130b2c61276fb.png"
},
"mineralwater-selters": {
"Mineral Water Selters":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/mineralwater-selters/Mineral Water Selters-2bac82327955c4357b51feff50bd6a2466705f85cb8e59875cb0192a161fd1d4.png"
},
"mineral-water-acquamorelli": {
"Mineral Water Acqua Morelli":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/mineral-water-acquamorelli/Mineral Water Acqua Morelli-4032ba6b4da7f79dcf005809423fc663cfb49199d6ce95f3cfe99a7705359ac4.png"
},
"mastercard-maestro": {
"MastercardMaestro":
"https://1dc3f33f6d.optimicdn.com/assets/establishment-backlinks/mastercard-maestro/MastercardMaestro-718680bc48d98c4a7417a14a62e4ba2d3af980361256739d980e7b453552b5d8.png"
}
}

View File

@ -8,7 +8,9 @@ def transfer_objects(data_type):
for app in apps.get_app_configs(): for app in apps.get_app_configs():
if exists(f"{app.path}/transfer_data.py"): if exists(f"{app.path}/transfer_data.py"):
card_module = SourceFileLoader("transfer", f"{app.path}/transfer_data.py").load_module() card_module = SourceFileLoader("transfer", f"{app.path}/transfer_data.py").load_module()
if not hasattr(card_module, "data_types") or not isinstance(card_module.data_types, dict) or len(card_module.data_types) < 1: if not hasattr(card_module, "data_types") \
or not isinstance(card_module.data_types, dict) \
or len(card_module.data_types) < 1:
continue continue
for module_data_type, transfer_funcs in card_module.data_types.items(): for module_data_type, transfer_funcs in card_module.data_types.items():

View File

@ -11,7 +11,6 @@ from django.http.request import HttpRequest
from django.utils.timezone import datetime from django.utils.timezone import datetime
from rest_framework import status from rest_framework import status
from rest_framework.request import Request from rest_framework.request import Request
from os.path import exists
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -64,7 +63,7 @@ def image_path(instance, filename):
def svg_image_path(instance, filename): def svg_image_path(instance, filename):
"""Determine SVG path method.""" """Determine SVG path method."""
filename = '%s.svg' % generate_code() filename = '%s.svg' % generate_code()
return 'image/svg/%s/%s/%s' % ( return 'svg/%s/%s/%s' % (
instance._meta.model_name, instance._meta.model_name,
datetime.now().strftime(settings.REST_DATE_FORMAT), datetime.now().strftime(settings.REST_DATE_FORMAT),
filename) filename)

View File

@ -78,6 +78,8 @@ def translate_field(self, field_name):
# fallback # fallback
if value is None: if value is None:
value = field.get(get_default_locale()) value = field.get(get_default_locale())
if value is None:
value = field.get(next(iter(field.keys()), None))
return value return value
return None return None
return translate return translate

View File

@ -6,6 +6,7 @@ from django.db import models
from django.db.models import Q, F from django.db.models import Q, F
from utils.methods import get_contenttype from utils.methods import get_contenttype
from utils.models import TJSONField
class ContentTypeQuerySetMixin(models.QuerySet): class ContentTypeQuerySetMixin(models.QuerySet):
@ -56,3 +57,24 @@ class RelatedObjectsCountMixin(models.QuerySet):
return self._annotate_related_objects_count()\ return self._annotate_related_objects_count()\
.annotate(all_related_count=exp)\ .annotate(all_related_count=exp)\
.filter(all_related_count__gt=count) .filter(all_related_count__gt=count)
class TranslationQuerysetMixin(models.QuerySet):
def _get_translatable_fields(self):
"""Get translatable fields from model."""
translatable_fields = []
model = self.model
for field in model._meta.fields:
field_name = field.name
if isinstance(field, TJSONField):
translatable_fields.append(field_name)
return translatable_fields
def _get_translatable_field_filters(self, locale: str):
"""Generate filters for filtering TJSON objects that has necessary locale."""
return {f'{field}__has_key': locale for field in self._get_translatable_fields()}
def has_translation(self, locale: str):
"""Filter objects by locale from request."""
return self.filter(**self._get_translatable_field_filters(locale))

View File

@ -74,7 +74,7 @@ PROJECT_APPS = [
'comment.apps.CommentConfig', 'comment.apps.CommentConfig',
'favorites.apps.FavoritesConfig', 'favorites.apps.FavoritesConfig',
'rating.apps.RatingConfig', 'rating.apps.RatingConfig',
'transfer.apps.TransferConfig', # 'transfer.apps.TransferConfig',
'tag.apps.TagConfig', 'tag.apps.TagConfig',
'product.apps.ProductConfig', 'product.apps.ProductConfig',
] ]
@ -157,16 +157,16 @@ DATABASES = {
'HOST': os.environ.get('DB_HOSTNAME'), 'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'), 'PORT': os.environ.get('DB_PORT'),
}, },
'legacy': { # 'legacy': {
'ENGINE': 'django.db.backends.mysql', # 'ENGINE': 'django.db.backends.mysql',
'HOST': '172.17.0.1', # # 'HOST': '172.17.0.1',
# 'HOST': '172.23.0.1', # # 'HOST': '172.23.0.1',
# 'HOST': 'mysql_db', # 'HOST': 'mysql_db',
'PORT': 3306, # 'PORT': 3306,
'NAME': 'dev', # 'NAME': 'dev',
'USER': 'dev', # 'USER': 'dev',
'PASSWORD': 'octosecret123' # 'PASSWORD': 'octosecret123'
} # }
} }
@ -262,8 +262,8 @@ OAUTH2_SOCIAL_AUTH_GRANT_TYPE = 'convert_token'
OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application' OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application'
# Facebook configuration # Facebook configuration
SOCIAL_AUTH_FACEBOOK_KEY = '386843648701452' SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('FB_APPLICATION_ID')
SOCIAL_AUTH_FACEBOOK_SECRET = 'a71cf0bf3980843a8f1ea74c6d805fd7' SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('FB_PRIVATE_KEY')
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', ] SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', ]
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email', 'fields': 'id, name, email',
@ -490,3 +490,8 @@ PHONENUMBER_DB_FORMAT = 'NATIONAL'
PHONENUMBER_DEFAULT_REGION = "FR" PHONENUMBER_DEFAULT_REGION = "FR"
FALLBACK_LOCALE = 'en-GB' FALLBACK_LOCALE = 'en-GB'
# TMP TODO remove it later
# Временный хардкод для демонстрации 4 ноября, потом удалить!
ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop']
NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership']

View File

@ -6,6 +6,8 @@ from sentry_sdk.integrations.django import DjangoIntegration
ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0'] ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0']
CSRF_TRUSTED_ORIGINS = ['.gm.id-east.ru', ]
SEND_SMS = False SEND_SMS = False
SMS_CODE_SHOW = True SMS_CODE_SHOW = True
USE_CELERY = True USE_CELERY = True
@ -38,7 +40,7 @@ sentry_sdk.init(
# TMP ( TODO remove it later) # TMP ( TODO remove it later)
# Временный хардкод для демонстрации 4 ноября, потом удалить! # Временный хардкод для демонстрации 4 ноября, потом удалить!
HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] HARDCODED_INTERNATIONAL_NEWS_IDS = [1460, 1471, 1482, 1484, 1611, 1612]
# Database # Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
@ -52,12 +54,16 @@ DATABASES = {
'HOST': os.environ.get('DB_HOSTNAME'), 'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'), 'PORT': os.environ.get('DB_PORT'),
}, },
'legacy': { # 'legacy': {
'ENGINE': 'django.db.backends.mysql', # 'ENGINE': 'django.db.backends.mysql',
'HOST': os.environ.get('MYSQL_HOSTNAME'), # 'HOST': os.environ.get('MYSQL_HOSTNAME'),
'PORT': os.environ.get('MYSQL_PORT'), # 'PORT': os.environ.get('MYSQL_PORT'),
'NAME': os.environ.get('MYSQL_DATABASE'), # 'NAME': os.environ.get('MYSQL_DATABASE'),
'USER': os.environ.get('MYSQL_USER'), # 'USER': os.environ.get('MYSQL_USER'),
'PASSWORD': os.environ.get('MYSQL_PASSWORD') # 'PASSWORD': os.environ.get('MYSQL_PASSWORD')
} # }
} }
BROKER_URL = 'redis://localhost:6379/1'
CELERY_RESULT_BACKEND = BROKER_URL
CELERY_BROKER_URL = BROKER_URL

View File

@ -1,10 +1,67 @@
"""Production settings.""" """Production settings."""
from .base import * from .base import *
from .amazon_s3 import * from .amazon_s3 import *
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
ALLOWED_HOSTS = ['*.next.gaultmillau.com', 'api.gaultmillau.com']
CSRF_TRUSTED_ORIGINS = ['.next.gaultmillau.com', ]
SEND_SMS = False
SMS_CODE_SHOW = True
USE_CELERY = True
SCHEMA_URI = 'http'
DEFAULT_SUBDOMAIN = 'www'
SITE_DOMAIN_URI = 'gaultmillau.com'
DOMAIN_URI = 'next.gaultmillau.com'
# ELASTICSEARCH SETTINGS
ELASTICSEARCH_DSL = {
'default': {
'hosts': 'elasticsearch:9200'
}
}
ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment',
}
sentry_sdk.init(
dsn="https://35d9bb789677410ab84a822831c6314f@sentry.io/1729093",
integrations=[DjangoIntegration()]
)
# TMP ( TODO remove it later)
# Временный хардкод для демонстрации 4 ноября, потом удалить!
HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17]
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USERNAME'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOSTNAME'),
'PORT': os.environ.get('DB_PORT'),
},
}
BROKER_URL = 'redis://localhost:6379/1'
CELERY_RESULT_BACKEND = BROKER_URL
CELERY_BROKER_URL = BROKER_URL
# Booking API configuration # Booking API configuration
GUESTONLINE_SERVICE = 'https://api.guestonline.fr/' GUESTONLINE_SERVICE = 'https://api.guestonline.fr/'
GUESTONLINE_TOKEN = '' GUESTONLINE_TOKEN = ''
LASTABLE_SERVICE = '' LASTABLE_SERVICE = ''
LASTABLE_TOKEN = '' LASTABLE_TOKEN = ''
LASTABLE_PROXY = '' LASTABLE_PROXY = ''

View File

@ -30,4 +30,4 @@ ELASTICSEARCH_INDEX_NAMES = {
# TMP ( TODO remove it later) # TMP ( TODO remove it later)
# Временный хардкод для демонстрации 4 ноября, потом удалить! # Временный хардкод для демонстрации 4 ноября, потом удалить!
HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] HARDCODED_INTERNATIONAL_NEWS_IDS = [1460, 1471, 1482, 1484, 1611, 1612]

View File

@ -42,7 +42,7 @@ django-storages==1.7.2
sorl-thumbnail==12.5.0 sorl-thumbnail==12.5.0
mysqlclient==1.4.4 # mysqlclient==1.4.4
PyYAML==5.1.2 PyYAML==5.1.2
# temp solution # temp solution