in progress

This commit is contained in:
Anatoly 2019-11-28 15:31:47 +03:00
parent ca7d7f0fa8
commit eaeebae72a
8 changed files with 652 additions and 13 deletions

View File

@ -0,0 +1,120 @@
import re
from pprint import pprint
from django.core.management.base import BaseCommand
from tqdm import tqdm
from establishment.models import Establishment
from location.models import City
from location.models import WineRegion
from product.models import Product
from review.models import Review
from tag.models import Tag
from transfer.models import GuideElements
def decorator(f):
def decorate(self):
print(f'{"-"*20}start {f.__name__}{"-"*20}')
f(self)
print(f'{"-"*20}end {f.__name__}{"-"*20}\n')
return decorate
class Command(BaseCommand):
help = """Check guide dependencies."""
@decorator
def count_of_guide_relative_dependencies(self):
for field in GuideElements._meta.fields:
if field.name not in ['id', 'lft', 'rgt', 'depth',
'children_count', 'parent', 'order_number']:
filters = {f'{field.name}__isnull': False, }
qs = GuideElements.objects.filter(**filters).values_list(field.name, flat=True)
print(f"COUNT OF {field.name}'s: {len(set(qs))}")
@decorator
def check_regions(self):
wine_region_old_ids = set(GuideElements.objects.filter(wine_region_id__isnull=False)
.values_list('wine_region_id', flat=True))
not_existed_wine_regions = []
for old_id in tqdm(wine_region_old_ids):
if not WineRegion.objects.filter(old_id=old_id).exists():
not_existed_wine_regions.append(old_id)
print(f'NOT EXISTED WINE REGIONS: {len(not_existed_wine_regions)}')
pprint(f'{not_existed_wine_regions}')
@decorator
def check_establishments(self):
establishment_old_ids = set(GuideElements.objects.filter(establishment_id__isnull=False)
.values_list('establishment_id', flat=True))
not_existed_establishments = []
for old_id in tqdm(establishment_old_ids):
if not Establishment.objects.filter(old_id=old_id).exists():
not_existed_establishments.append(old_id)
print(f'NOT EXISTED ESTABLISHMENTS: {len(not_existed_establishments)}')
pprint(f'{not_existed_establishments}')
@decorator
def check_reviews(self):
review_old_ids = set(GuideElements.objects.filter(review_id__isnull=False)
.values_list('review_id', flat=True))
not_existed_reviews = []
for old_id in tqdm(review_old_ids):
if not Review.objects.filter(old_id=old_id).exists():
not_existed_reviews.append(old_id)
print(f'NOT EXISTED REVIEWS: {len(not_existed_reviews)}')
pprint(f'{not_existed_reviews}')
@decorator
def check_wines(self):
wine_old_ids = set(GuideElements.objects.filter(wine_id__isnull=False)
.values_list('wine_id', flat=True))
not_existed_wines = []
for old_id in tqdm(wine_old_ids):
if not Product.objects.filter(old_id=old_id).exists():
not_existed_wines.append(old_id)
print(f'NOT EXISTED WINES: {len(not_existed_wines)}')
pprint(f'{not_existed_wines}')
@decorator
def check_wine_color(self):
raw_wine_color_nodes = set(GuideElements.objects.exclude(color__iexact='')
.filter(color__isnull=False)
.values_list('color', flat=True))
raw_wine_colors = [i[:-11] for i in raw_wine_color_nodes]
raw_wine_color_index_names = []
re_exp = '[A-Z][^A-Z]*'
for raw_wine_color in tqdm(raw_wine_colors):
result = re.findall(re_exp, rf'{raw_wine_color}')
if result and len(result) >= 2:
wine_color = '-'.join(result)
else:
wine_color = result[0]
raw_wine_color_index_names.append(wine_color.lower())
not_existed_wine_colors = []
for index_name in raw_wine_color_index_names:
if not Tag.objects.filter(value=index_name).exists():
not_existed_wine_colors.append(index_name)
print(f'NOT EXISTED WINE COLOR: {len(not_existed_wine_colors)}')
pprint(f'{not_existed_wine_colors}')
@decorator
def check_cities(self):
city_old_ids = set(GuideElements.objects.filter(city_id__isnull=False)
.values_list('city_id', flat=True))
not_existed_cities = []
for old_id in tqdm(city_old_ids):
if not City.objects.filter(old_id=old_id).exists():
not_existed_cities.append(old_id)
print(f'NOT EXISTED CITIES: {len(not_existed_cities)}')
pprint(f'{not_existed_cities}')
def handle(self, *args, **kwargs):
self.count_of_guide_relative_dependencies()
self.check_regions()
self.check_establishments()
self.check_reviews()
self.check_wines()
self.check_wine_color()
self.check_cities()

View File

@ -0,0 +1,78 @@
# Generated by Django 2.2.7 on 2019-11-27 10:47
import django.contrib.postgres.fields.jsonb
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('main', '0039_sitefeature_old_id'),
('collection', '0017_collection_old_id'),
]
operations = [
migrations.CreateModel(
name='GuideType',
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')),
('name', models.SlugField(max_length=255, unique=True, verbose_name='code')),
],
options={
'verbose_name': 'guide type',
'verbose_name_plural': 'guide types',
},
),
migrations.RemoveField(
model_name='guide',
name='advertorials',
),
migrations.RemoveField(
model_name='guide',
name='collection',
),
migrations.RemoveField(
model_name='guide',
name='parent',
),
migrations.AddField(
model_name='guide',
name='old_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='guide',
name='site',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site settings'),
),
migrations.AddField(
model_name='guide',
name='slug',
field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='slug'),
),
migrations.AddField(
model_name='guide',
name='state',
field=models.PositiveSmallIntegerField(choices=[(0, 'built'), (1, 'waiting'), (2, 'removing'), (3, 'building')], default=1, verbose_name='state'),
),
migrations.AddField(
model_name='guide',
name='vintage',
field=models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2100)], verbose_name='guide vintage year'),
),
migrations.AddField(
model_name='guide',
name='guide_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='collection.GuideType', verbose_name='type'),
),
migrations.AlterField(
model_name='guide',
name='start',
field=models.DateTimeField(null=True, verbose_name='start'),
),
]

View File

@ -1,6 +1,8 @@
from django.contrib.contenttypes.fields import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
import re
from django.utils.translation import gettext_lazy as _
from utils.models import ProjectBaseMixin, URLImageMixin
@ -85,26 +87,64 @@ class Collection(ProjectBaseMixin, CollectionDateMixin,
verbose_name_plural = _('collections')
class GuideTypeQuerySet(models.QuerySet):
"""QuerySet for model GuideType."""
class GuideType(ProjectBaseMixin):
"""GuideType model."""
name = models.SlugField(max_length=255, unique=True,
verbose_name=_('code'))
objects = GuideTypeQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide type')
verbose_name_plural = _('guide types')
def __str__(self):
"""Overridden str dunder method."""
return self.name
class GuideQuerySet(models.QuerySet):
"""QuerySet for Guide."""
def by_collection_id(self, collection_id):
"""Filter by collection id"""
return self.filter(collection=collection_id)
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""Guide model."""
parent = models.ForeignKey(
'self', verbose_name=_('parent'), on_delete=models.CASCADE,
null=True, blank=True, default=None
BUILT = 0
WAITING = 1
REMOVING = 2
BUILDING = 3
STATE_CHOICES = (
(BUILT, 'built'),
(WAITING, 'waiting'),
(REMOVING, 'removing'),
(BUILDING, 'building'),
)
advertorials = JSONField(
_('advertorials'), null=True, blank=True,
default=None, help_text='{"key":"value"}')
collection = models.ForeignKey(Collection, on_delete=models.CASCADE,
null=True, blank=True, default=None,
verbose_name=_('collection'))
start = models.DateTimeField(null=True,
verbose_name=_('start'))
vintage = models.IntegerField(validators=[MinValueValidator(1900),
MaxValueValidator(2100)],
null=True,
verbose_name=_('guide vintage year'))
slug = models.SlugField(max_length=255, unique=True, null=True,
verbose_name=_('slug'))
guide_type = models.ForeignKey('GuideType', on_delete=models.PROTECT,
null=True,
verbose_name=_('type'))
site = models.ForeignKey('main.SiteSettings', on_delete=models.SET_NULL,
null=True,
verbose_name=_('site settings'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('state'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideQuerySet.as_manager()
@ -116,3 +156,71 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
def __str__(self):
"""String method."""
return f'{self.name}'
class AdvertorialQuerySet(models.QuerySet):
"""QuerySet for model Advertorial."""
class Advertorial(ProjectBaseMixin):
"""Guide advertorial model."""
number_of_pages = models.PositiveIntegerField(
verbose_name=_('number of pages'),
help_text=_('the total number of reserved pages'))
right_pages = models.PositiveIntegerField(
verbose_name=_('number of right pages'),
help_text=_('the number of right pages (which are part of total number).'))
old_id = models.IntegerField(blank=True, null=True)
objects = AdvertorialQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('advertorial')
verbose_name_plural = _('advertorials')
class GuideFilterQuerySet(models.QuerySet):
"""QuerySet for model GuideFilter."""
class GuideFilter(ProjectBaseMixin):
"""Guide filter model."""
establishment_type_json = JSONField(blank=True, null=True,
verbose_name='establishment types')
country_code_json = JSONField(blank=True, null=True,
verbose_name='countries')
region_code_json = JSONField(blank=True, null=True,
verbose_name='regions')
sub_region_code_json = JSONField(blank=True, null=True,
verbose_name='sub regions')
wine_region_json = JSONField(blank=True, null=True,
verbose_name='wine regions')
wine_classification_json = JSONField(blank=True, null=True,
verbose_name='wine classifications')
wine_color_json = JSONField(blank=True, null=True,
verbose_name='wine colors')
wine_type_json = JSONField(blank=True, null=True,
verbose_name='wine types')
with_mark = models.BooleanField(default=True,
verbose_name=_('with mark'),
help_text=_('exclude empty marks?'))
locale_json = JSONField(blank=True, null=True,
verbose_name='locales')
max_mark = models.PositiveSmallIntegerField(verbose_name=_('max mark'),
help_text=_('mark under'))
min_mark = models.PositiveSmallIntegerField(verbose_name=_('min mark'),
help_text=_('mark over'))
review_vintage_json = JSONField(verbose_name='review vintage years')
review_state_json = JSONField(blank=True, null=True,
verbose_name='review states')
guide = models.OneToOneField(Guide, on_delete=models.CASCADE,
verbose_name=_('guide'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideFilterQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide filter')
verbose_name_plural = _('guide filters')

View File

@ -0,0 +1,37 @@
from pprint import pprint
from transfer.models import Guides, GuideFilters
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
def transfer_guide():
"""Transfer Guide model."""
queryset = Guides.objects.exclude(title__icontains='test')
serialized_data = GuideSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer guide errors: {serialized_data.errors}")
def transfer_guide_filter():
"""Transfer GuideFilter model."""
queryset = GuideFilters.objects.all()
serialized_data = GuideFilterSerializer(
data=list(queryset.values()),
many=True)
if serialized_data.is_valid():
serialized_data.save()
else:
pprint(f"transfer guide filter errors: {serialized_data.errors}")
data_types = {
'guides': [
transfer_guide,
],
'guide_filters': [
transfer_guide_filter,
]
}

View File

@ -41,6 +41,8 @@ class Command(BaseCommand):
'newsletter_subscriber', # подписчики на рассылку - переносить после переноса пользователей №1
'purchased_plaques', # №6 - перенос купленных тарелок
'fill_city_gallery', # №3 - перенос галереи городов
'guides',
'guide_filters',
]
def handle(self, *args, **options):

View File

@ -5,11 +5,19 @@
# * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
import yaml
from django.contrib.gis.db import models
from transfer.mixins import MigrateMixin
def convert_entry(loader, node):
return {e[0]: e[1] for e in loader.construct_pairs(node)}
yaml.add_constructor('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', convert_entry)
# models.ForeignKey(ForeignModel, models.DO_NOTHING, blank=True, null=True)
class Sites(MigrateMixin):

View File

@ -0,0 +1,283 @@
from itertools import chain
import yaml
from pycountry import countries, subdivisions
from rest_framework import serializers
from collection.models import Guide, GuideType, GuideFilter
from establishment.models import EstablishmentType
from location.models import Country, Region
from main.models import SiteSettings
from transfer.mixins import TransferSerializerMixin
class GuideSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
title = serializers.CharField()
vintage = serializers.IntegerField()
slug = serializers.CharField()
state = serializers.CharField()
site_id = serializers.IntegerField()
inserter_field = serializers.CharField()
class Meta:
model = Guide
fields = (
'id',
'title',
'vintage',
'slug',
'state',
'site_id',
'inserter_field',
)
def validate(self, attrs):
"""Overridden validate method."""
attrs['old_id'] = attrs.pop('id')
attrs['name'] = attrs.pop('title')
attrs['vintage'] = int(attrs.pop('vintage'))
attrs['state'] = self.get_state(attrs.pop('state'))
attrs['site'] = self.get_site(attrs.pop('site_id'))
attrs['guide_type'] = self.get_guide_type(attrs.pop('inserter_field'))
return attrs
def get_state(self, state: str):
if state == 'built':
return Guide.BUILT
elif state == 'removing':
return Guide.REMOVING
elif state == 'building':
return Guide.BUILDING
else:
return Guide.WAITING
def get_site(self, site_id):
qs = SiteSettings.objects.filter(old_id=site_id)
if qs.exists():
return qs.first()
def get_guide_type(self, inserter_field):
guide_type, _ = GuideType.objects.get_or_create(name=inserter_field)
return guide_type
class GuideFilterSerializer(TransferSerializerMixin):
id = serializers.IntegerField()
year = serializers.CharField()
establishment_type = serializers.CharField(allow_null=True)
countries = serializers.CharField(allow_null=True)
regions = serializers.CharField(allow_null=True)
subregions = serializers.CharField(allow_null=True)
wine_regions = serializers.CharField(allow_null=True)
wine_classifications = serializers.CharField(allow_null=True)
wine_colors = serializers.CharField(allow_null=True)
wine_types = serializers.CharField(allow_null=True)
locales = serializers.CharField(allow_null=True)
states = serializers.CharField(allow_null=True)
max_mark = serializers.IntegerField(allow_null=True)
min_mark = serializers.IntegerField(allow_null=True)
marks_only = serializers.NullBooleanField()
guide_id = serializers.IntegerField()
class Meta:
model = GuideFilter
fields = (
'id',
'year',
'establishment_type',
'countries',
'regions',
'subregions',
'wine_regions',
'wine_classifications',
'wine_colors',
'wine_types',
'max_mark',
'min_mark',
'marks_only',
'locales',
'states',
'guide_id',
)
@staticmethod
def parse_ruby_helper(raw_value: str):
"""Parse RubyActiveSupport records"""
def convert_entry(loader, node):
return {e[0]: e[1] for e in loader.construct_pairs(node)}
loader = yaml.Loader
loader.add_constructor('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', convert_entry)
return yaml.load(raw_value, Loader=loader) if raw_value else None
@staticmethod
def get_country_alpha_2(country_code_alpha3: str):
country = countries.get(alpha_3=country_code_alpha3.upper())
return {'code_alpha_2': country.alpha2.lower() if country else None,
'name': country.name if country else None,}
@staticmethod
def parse_dictionary(dictionary: dict):
"""
Exclude root values from dictionary.
Convert {key_2: [value_1, value_2]} into
[value_1, value_2]
"""
return list(chain.from_iterable(dictionary.values()))
@staticmethod
def parse_nested_dictionary(dictionary: dict):
"""
Exclude root values from dictionary.
Convert {key_1: {key_2: [value_1, value_2]}} into
[value_1, value_2]
"""
l = []
for i in dictionary:
l.append(list(chain.from_iterable(list(dictionary[i].values()))))
return list(chain.from_iterable(l))
def get_country(self, code_alpha_3: str) -> Country:
country = self.get_country_alpha_2(code_alpha_3)
country_name = country['name']
country_code = country['code_alpha_2']
if country_name and country_code:
country, _ = Country.objects.get_or_create(
code__icontains=country_code,
name__contains={'en-GB': country_name},
defaults={
'code': country_code,
'name': {'en-GB': country_name}
}
)
return country
def get_region(self, region_code_alpha_3: str,
country_code_alpha_3: str,
sub_region_code_alpha_3: str = None):
country = self.get_country(country_code_alpha_3)
country_code_alpha_2 = country.code.upper()
region_qs = Region.objects.filter(code__iexact=region_code_alpha_3,
country__code__iexact=country_code_alpha_2)
# If region isn't existed, check sub region for parent_code (region code)
if not region_qs.exists() and sub_region_code_alpha_3:
# sub region
subdivision = subdivisions.get(
code=f"{country_code_alpha_2}-{sub_region_code_alpha_3}")
if subdivision:
subdivision_region = subdivisions.get(parent_code=subdivision.parent_code)
obj = Region.objects.create(
name=subdivision_region.name,
code=subdivision_region.code,
country=country)
return obj
else:
return region_qs.first()
def validate_year(self, value):
return self.parse_ruby_helper(value)
def validate_establishment_type(self, value):
return self.parse_ruby_helper(value)
def validate_countries(self, value):
return self.parse_ruby_helper(value)
def validate_regions(self, value):
return self.parse_ruby_helper(value)
def validate_sub_regions(self, value):
return self.parse_ruby_helper(value)
def validate_wine_regions(self, value):
return self.parse_ruby_helper(value)
def validate_wine_classifications(self, value):
return self.parse_ruby_helper(value)
def validate_wine_colors(self, value):
return self.parse_ruby_helper(value)
def validate_wine_types(self, value):
return self.parse_ruby_helper(value)
def validate_locales(self, value):
return self.parse_ruby_helper(value)
def validate_states(self, value):
return self.parse_ruby_helper(value)
def validate(self, attrs):
sub_regions = attrs.pop('subregions')
regions = attrs.pop('regions')
attrs['old_id'] = attrs.pop('id')
attrs['review_vintage_json'] = self.get_review_vintage(self.attrs.pop('year'))
attrs['establishment_type_json'] = self.get_establishment_type_ids(
self.attrs.pop('establishment_type'))
attrs['country_code_json'] = self.get_country_ids(attrs.pop('country_json'))
attrs['region_json'] = self.get_region_ids(regions, sub_regions)
attrs['sub_region_json'] = self.get_sub_region_ids(regions, sub_regions)
return attrs
def get_review_vintage(self, year):
return {'vintage': [int(i) for i in set(year) if i.isdigit()]}
def get_establishment_type_ids(self, establishment_types):
establishment_type_ids = []
for establishment_type in establishment_types:
establishment_type_qs = EstablishmentType.objects.filter(index_name__iexact=establishment_type)
if not establishment_type_qs.exists():
obj = EstablishmentType.objects.create(
name={'en-GB': establishment_type.capitalize},
index_name=establishment_type.lower())
establishment_type_ids.append(obj.id)
return {'id': establishment_type_ids}
def get_country_ids(self, country_codes_alpha_3):
country_ids = []
for code_alpha_3 in country_codes_alpha_3:
country_ids.append(self.get_country(code_alpha_3).id)
return {'id': country_ids}
def get_region_ids(self, regions, sub_regions):
region_ids = []
for country_code_alpha_3 in regions:
region_codes = regions[country_code_alpha_3]
for region_code_alpha_3 in region_codes:
region = self.get_region(
region_code_alpha_3=region_code_alpha_3,
country_code_alpha_3=country_code_alpha_3,
sub_region_code_alpha_3=sub_regions[country_code_alpha_3][region_code_alpha_3])
if region:
region_ids.append(region.id)
return {'id': region_ids}
def get_sub_region_ids(self, sub_regions):
sub_region_ids = []
for country_code_alpha_3 in sub_regions:
for region_code_alpha_3 in sub_regions[country_code_alpha_3]:
region_codes = sub_regions[country_code_alpha_3][region_code_alpha_3]
for sub_region_code_alpha_3 in region_codes:
region = self.get_region(
region_code_alpha_3=region_code_alpha_3,
country_code_alpha_3=country_code_alpha_3,
sub_region_code_alpha_3=sub_region_code_alpha_3)
if region:
subdivision = subdivisions.get(parent_code=region.code.upper())
if subdivision:
sub_region = Region.objects.create(
name=subdivision.name,
code=subdivisions.code,
parent_region=region,
country=region.country)
sub_region_ids.append(sub_region.id)
return {'id': sub_region_ids}
# {'FRA': ['H', 'U']} REGIONS
# {'FRA': {'N': ['32']}} SUB REGIONS

View File

@ -57,3 +57,6 @@ redis==3.2.0
django_redis==4.10.0 # used byes indexing cache
kombu==4.6.6
celery==4.3.0
# country information
pycountry==19.8.18