Merge branch 'develop' into feature/collect

This commit is contained in:
Виктор Гладких 2019-11-01 10:18:36 +03:00
commit 954d515a5a
25 changed files with 433 additions and 51 deletions

View File

@ -87,3 +87,8 @@ class MenuAdmin(BaseModelAdminMixin, admin.ModelAdmin):
return obj.category_translated
category_translated.short_description = _('category')
@admin.register(models.RatingStrategy)
class RatingStrategyAdmin(BaseModelAdminMixin, admin.ModelAdmin):
"""Admin conf for Rating Strategy model."""

View File

@ -1,5 +1,3 @@
from pprint import pprint
from django.core.management.base import BaseCommand
from django.db.models import Q
@ -19,8 +17,6 @@ class Command(BaseCommand):
Q(mark__isnull=True)
).filter(aasm_state='published').values_list('establishment_id', 'mark', 'updated_at')
print(queryset.count())
for es_id, new_mark, new_date in queryset:
try:
mark, date = valid_data[es_id]
@ -41,6 +37,5 @@ class Command(BaseCommand):
establishment.public_mark = int(value[0])
establishment.save()
count += 1
break
self.stdout.write(self.style.WARNING(f'Updated {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,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

@ -14,7 +14,6 @@ from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from pytz import timezone as ptz
from timezone_field import TimeZoneField
from collection.models import Collection
@ -384,7 +383,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
# todo: recalculate toque_number
def recalculate_toque_number(self):
self.toque_number = 4
toque_number = None
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):
if low_price is None or high_price is None:
@ -669,3 +674,62 @@ class SocialNetwork(models.Model):
def __str__(self):
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 None
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

@ -1,6 +1,8 @@
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 pprint import pprint
@ -131,6 +133,13 @@ def transfer_addresses():
pprint(f"Address serializer errors: {serialized_data.errors}")
def update_flags():
queryset = Country.objects.only("id", "code", "svg_image").filter(old_id__isnull=False)
for query in queryset:
query.svg_image = f"/image/image/10-31-2019/{query.code}.svg"
query.save()
data_types = {
"dictionaries": [
@ -138,5 +147,8 @@ data_types = {
transfer_regions,
transfer_cities,
transfer_addresses
],
"update_country_flag": [
update_flags
]
}

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

@ -130,7 +130,6 @@ class SiteSettingsQuerySet(models.QuerySet):
class SiteSettings(ProjectBaseMixin):
subdomain = models.CharField(max_length=255, db_index=True, unique=True,
verbose_name=_('Subdomain'))
country = models.OneToOneField(Country, on_delete=models.PROTECT,
@ -172,8 +171,9 @@ class SiteSettings(ProjectBaseMixin):
@property
def published_sitefeatures(self):
return self.sitefeature_set\
.filter(Q(published=True) and Q(feature__source__in=[PlatformMixin.WEB, PlatformMixin.ALL]))
return self.sitefeature_set. \
filter(Q(published=True) &
Q(feature__source__in=[PlatformMixin.WEB, PlatformMixin.ALL]))
@property
def site_url(self):
@ -281,10 +281,19 @@ class CarouselQuerySet(models.QuerySet):
class Carousel(models.Model):
"""Carousel model."""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True, default=None)
object_id = models.PositiveIntegerField(blank=True, null=True, default=None)
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)
active = models.BooleanField(_('old active'), default=False)
is_parse = models.BooleanField(_('is parse'), default=False)
objects = CarouselQuerySet.as_manager()
class Meta:

View File

@ -71,7 +71,8 @@ card = {
"facebook_page_url": "facebook_page_url",
"instagram_page_url": "instagram_page_url",
"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": {
# # Как работать c отношение OneToOneField
@ -133,7 +134,7 @@ card = {
"vintage_year": "year",
}
},
#TODO вопрос с content_type
# TODO вопрос с content_type
"relations": {
"AwardTypes": [(
("award_type", None),
@ -143,4 +144,4 @@ card = {
}
}
used_apps = ("locations", )
used_apps = ("locations",)

View File

@ -0,0 +1,19 @@
from pprint import pprint
from transfer.models import CarouselElements
from transfer.serializers.carousel import CarouselSerializer
def transfer_carousel():
queryset = CarouselElements.objects.all()
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

@ -37,7 +37,7 @@ class EstablishmentDocument(Document):
works_noon = fields.ListField(fields.IntegerField(
attr='works_noon'
))
# works_now = fields.BooleanField(attr='works_now')
works_now = fields.BooleanField(attr='works_now')
tags = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),

View File

@ -1,7 +1,7 @@
"""Search indexes filters."""
from elasticsearch_dsl.query import Q
from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend
from utils.models import get_current_language
from utils.models import get_current_locale
class CustomSearchFilterBackend(SearchFilterBackend):
@ -12,7 +12,7 @@ class CustomSearchFilterBackend(SearchFilterBackend):
field_name = field
if hasattr(view, 'search_fields') and hasattr(view, 'translated_search_fields'):
if field in view.translated_search_fields:
field_name = f'{field}.{get_current_language()}'
field_name = f'{field}.{get_current_locale()}'
return field_name
def construct_search(self, request, view):

View File

@ -1,6 +1,6 @@
"""Search indexes utils."""
from django_elasticsearch_dsl import fields
from utils.models import get_current_language
from utils.models import get_current_locale, get_default_locale
# object field properties
@ -19,4 +19,8 @@ def get_translated_value(value):
field_dict = value.to_dict()
elif isinstance(value, dict):
field_dict = value
return field_dict.get(get_current_language())
value = field_dict.get(get_current_locale())
# fallback
if value is None:
value = field_dict.get(get_default_locale())
return value

View File

@ -139,7 +139,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
'lookups': [
constants.LOOKUP_QUERY_IN,
],
},
},
'works_noon': {
'field': 'works_noon',
'lookups': [
@ -152,12 +152,12 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
constants.LOOKUP_QUERY_IN,
],
},
# 'works_now': {
# 'field': 'works_now',
# 'lookups': [
# constants.LOOKUP_FILTER_TERM,
# ]
# },
'works_now': {
'field': 'works_now',
'lookups': [
constants.LOOKUP_FILTER_TERM,
]
},
}
geo_spatial_filter_fields = {

View File

@ -14,7 +14,8 @@ class TagBaseSerializer(serializers.ModelSerializer):
# todo: refactor this
# label_translated = TranslatedField()
label_translated = serializers.CharField(source='value')
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:
"""Meta class."""
@ -23,6 +24,7 @@ class TagBaseSerializer(serializers.ModelSerializer):
fields = (
'id',
'label_translated',
'index_name',
)
@ -43,7 +45,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
# todo: refactor this
# label_translated = TranslatedField()
label_translated = serializers.CharField(source='index_name')
label_translated = serializers.CharField(source='index_name', read_only=True, allow_null=True)
tags = TagBaseSerializer(many=True, read_only=True)
class Meta:

View File

@ -8,7 +8,7 @@ class Command(BaseCommand):
"""Типы данных для трансфера
ВНИМАНИЕ: первые буквы типов данных должны быть уникальны!
"""
DATA_TYPES = [
SHORT_DATA_TYPES = [
'dictionaries',
'news',
'account',
@ -21,20 +21,40 @@ class Command(BaseCommand):
'overlook',
'tmp',
'menu',
'location_establishment'
'location_establishment',
'whirligig',
]
LONG_DATA_TYPES = [
'update_country_flag'
]
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:
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)
transfer_objects(data_type[0])
data_type = list(set(option for option in options.keys() if options[option]) & set(self.SHORT_DATA_TYPES))
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")
exit(1)
else:
data_type = data_type[0]
else:
data_type = data_type[0]
transfer_objects(data_type)
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"""
for option_type in self.DATA_TYPES:
for option_type in self.SHORT_DATA_TYPES:
parser.add_argument(
f'-{option_type[:1]}',
f'--{option_type}',

View File

@ -938,3 +938,25 @@ class KeyValueMetadatumKeyValueMetadatumEstablishments(MigrateMixin):
# class Meta:
# managed = False
# db_table = 'wine_types'
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_id = models.IntegerField(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'

View File

@ -0,0 +1,59 @@
from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from establishment.models import Establishment
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()
def create(self, validated_data):
object_id, content_type_id = self.get_content_type(validated_data)
validated_data.update({
'active': bool(int(validated_data['active'])),
'content_type_id': content_type_id,
'object_id': object_id,
'is_parse': bool(object_id),
})
obj = Carousel.objects.create(**validated_data)
return obj
@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

@ -6,8 +6,8 @@ from establishment.models import Establishment, ContactEmail, ContactPhone, Esta
from location.models import Address
from timetable.models import Timetable
from utils.legacy_parser import parse_legacy_schedule_content
from utils.serializers import TimeZoneChoiceField
from utils.slug_generator import generate_unique_slug
from pytz import timezone as ptz
class EstablishmentSerializer(serializers.ModelSerializer):
@ -26,7 +26,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
twitter = serializers.CharField(allow_null=True, allow_blank=True)
booking = serializers.CharField(allow_null=True, allow_blank=True)
state = serializers.CharField(allow_null=True)
tz = serializers.CharField()
tz = TimeZoneChoiceField()
created = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S')
class Meta:
@ -59,7 +59,6 @@ class EstablishmentSerializer(serializers.ModelSerializer):
'establishment_type_id': self.get_type(data),
'is_publish': data.get('state') == 'published',
})
data['tz'] = ptz(data['tz'])
data.pop('location')
data.pop('type')
data.pop('state')

View File

@ -8,7 +8,9 @@ def transfer_objects(data_type):
for app in apps.get_app_configs():
if exists(f"{app.path}/transfer_data.py"):
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
for module_data_type, transfer_funcs in card_module.data_types.items():

View File

@ -10,6 +10,7 @@ from django.contrib.postgres.fields.jsonb import KeyTextTransform
from django.utils import timezone
from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _, get_language
from configuration.models import TranslationSettings
from easy_thumbnails.fields import ThumbnailerImageField
from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.fields import ImageField as SORLImageField
@ -59,13 +60,26 @@ def to_locale(language):
return language + '-' + country
def get_current_locale():
"""Get current language."""
return to_locale(get_language())
def get_default_locale():
return TranslationSettings.get_solo().default_language or \
settings.FALLBACK_LOCALE
def translate_field(self, field_name):
def translate(self):
field = getattr(self, field_name)
if isinstance(field, dict):
return field.get(to_locale(get_language()))
value = field.get(to_locale(get_language()))
# fallback
if value is None:
value = field.get(get_default_locale())
return value
return None
return translate
@ -111,15 +125,10 @@ class TranslatedFieldsMixin:
if self.STR_FIELD_NAME:
field = getattr(self, getattr(self, 'STR_FIELD_NAME'))
if isinstance(field, dict):
value = field.get(get_current_language())
value = field.get(get_current_locale())
return value if value else super(TranslatedFieldsMixin, self).__str__()
def get_current_language():
"""Get current language."""
return to_locale(get_language())
class OAuthProjectMixin:
"""OAuth2 mixin for project GM"""

View File

@ -159,9 +159,9 @@ DATABASES = {
},
'legacy': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '172.17.0.1',
# 'HOST': '172.17.0.1',
# 'HOST': '172.23.0.1',
# 'HOST': 'mysql_db',
'HOST': 'mysql_db',
'PORT': 3306,
'NAME': 'dev',
'USER': 'dev',
@ -488,3 +488,5 @@ MEDIA_LOCATION = 'media'
PHONENUMBER_DB_FORMAT = 'NATIONAL'
PHONENUMBER_DEFAULT_REGION = "FR"
FALLBACK_LOCALE = 'en-GB'