From e839094e28189abc3e3556e194611c57fec094b5 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 22 Aug 2019 14:23:02 +0300 Subject: [PATCH] Site settings and determine user location over ip --- .../migrations/0002_remove_country_name.py | 17 ++++ .../migrations/0003_auto_20190821_1407.py | 35 +++++++ .../migrations/0004_auto_20190822_0710.py | 23 +++++ apps/location/models.py | 29 +++++- apps/location/serializers.py | 7 +- apps/location/urls.py | 8 +- apps/location/views.py | 40 +++----- apps/main/methods.py | 61 ++++++++++++ .../migrations/0003_auto_20190822_0726.py | 18 ++++ .../migrations/0004_auto_20190822_0726.py | 18 ++++ .../migrations/0005_auto_20190822_0727.py | 25 +++++ .../migrations/0006_auto_20190822_1118.py | 18 ++++ apps/main/models.py | 20 +++- apps/main/serializers.py | 4 + apps/main/urls.py | 18 ++-- apps/main/views.py | 99 ++++++++++--------- apps/translation/views.py | 1 + apps/utils/methods.py | 6 +- project/settings/development.py | 2 + project/settings/local.py | 4 +- 20 files changed, 353 insertions(+), 100 deletions(-) create mode 100644 apps/location/migrations/0002_remove_country_name.py create mode 100644 apps/location/migrations/0003_auto_20190821_1407.py create mode 100644 apps/location/migrations/0004_auto_20190822_0710.py create mode 100644 apps/main/methods.py create mode 100644 apps/main/migrations/0003_auto_20190822_0726.py create mode 100644 apps/main/migrations/0004_auto_20190822_0726.py create mode 100644 apps/main/migrations/0005_auto_20190822_0727.py create mode 100644 apps/main/migrations/0006_auto_20190822_1118.py diff --git a/apps/location/migrations/0002_remove_country_name.py b/apps/location/migrations/0002_remove_country_name.py new file mode 100644 index 00000000..ff928bac --- /dev/null +++ b/apps/location/migrations/0002_remove_country_name.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-08-21 13:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='country', + name='name', + ), + ] diff --git a/apps/location/migrations/0003_auto_20190821_1407.py b/apps/location/migrations/0003_auto_20190821_1407.py new file mode 100644 index 00000000..548e2a70 --- /dev/null +++ b/apps/location/migrations/0003_auto_20190821_1407.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.4 on 2019-08-21 14:07 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0002_remove_country_name'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='code', + field=models.CharField(blank=True, default=None, max_length=255, null=True, unique=True, verbose_name='Code'), + ), + migrations.AddField( + model_name='country', + name='created', + field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created'), + ), + migrations.AddField( + model_name='country', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='country', + name='name', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en":"some text"}', null=True, verbose_name='Text'), + ), + ] diff --git a/apps/location/migrations/0004_auto_20190822_0710.py b/apps/location/migrations/0004_auto_20190822_0710.py new file mode 100644 index 00000000..ef55fcf9 --- /dev/null +++ b/apps/location/migrations/0004_auto_20190822_0710.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-08-22 07:10 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0003_auto_20190821_1407'), + ] + + operations = [ + migrations.AlterModelOptions( + name='country', + options={'verbose_name': 'Country', 'verbose_name_plural': 'Countries'}, + ), + migrations.AlterField( + model_name='country', + name='name', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, help_text='{"en":"some text"}', null=True, verbose_name='Name'), + ), + ] diff --git a/apps/location/models.py b/apps/location/models.py index d748dfd7..8cd8fc08 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -1,21 +1,37 @@ +"""Location app models.""" +from django.contrib.postgres.fields import JSONField from django.contrib.gis.db import models from django.utils.translation import gettext_lazy as _ +from utils.models import ProjectBaseMixin, LocaleManagerMixin -class Country(models.Model): +class CountryManager(LocaleManagerMixin): + """Extended manager for Country model.""" + + +class Country(ProjectBaseMixin): """Country model.""" - name = models.CharField(_('name'), max_length=250) + + name = JSONField(null=True, blank=True, default=None, + verbose_name=_('Name'), help_text='{"en":"some text"}') + code = models.CharField(max_length=255, blank=True, null=True, + default=None, unique=True, verbose_name=_('Code')) + + objects = CountryManager() class Meta: - verbose_name_plural = _('countries') - verbose_name = _('country') + """Meta class.""" + + verbose_name_plural = _('Countries') + verbose_name = _('Country') def __str__(self): - return self.name + return self.code class Region(models.Model): """Region model.""" + name = models.CharField(_('name'), max_length=250) code = models.CharField(_('code'), max_length=250) parent_region = models.ForeignKey( @@ -25,6 +41,8 @@ class Region(models.Model): Country, verbose_name=_('country'), on_delete=models.CASCADE) class Meta: + """Meta class.""" + verbose_name_plural = _('regions') verbose_name = _('region') @@ -34,6 +52,7 @@ class Region(models.Model): class City(models.Model): """Region model.""" + name = models.CharField(_('name'), max_length=250) code = models.CharField(_('code'), max_length=250) region = models.ForeignKey( diff --git a/apps/location/serializers.py b/apps/location/serializers.py index 2ffeed69..0e4efa7e 100644 --- a/apps/location/serializers.py +++ b/apps/location/serializers.py @@ -5,16 +5,21 @@ from django.contrib.gis.geos import Point class CountrySerializer(serializers.ModelSerializer): """Country serializer.""" + + name_trans = serializers.CharField(read_only=True, allow_null=True) + class Meta: model = models.Country fields = [ 'id', - 'name' + 'code', + 'name_trans', ] class RegionSerializer(serializers.ModelSerializer): """Region serializer""" + country = CountrySerializer() class Meta: diff --git a/apps/location/urls.py b/apps/location/urls.py index b8c71db3..5f90c1b8 100644 --- a/apps/location/urls.py +++ b/apps/location/urls.py @@ -6,11 +6,9 @@ from location import views app_name = 'location' urlpatterns = [ - path('country/', views.CountryListView.as_view(), name='country_list'), - path('country/create/', views.CountryCreateView.as_view(), name='country_create'), - path('country//', views.CountryRetrieveView.as_view(), name='country_retrieve'), - path('country//delete/', views.CountryDestroyView.as_view(), name='country_destroy'), - path('country//update/', views.CountryUpdateView.as_view(), name='country_update'), + path('country/', views.CountryListView.as_view(), name='country-list'), + path('country//', views.CountryRetrieveView.as_view(), + name='country-retrieve'), path('region/', views.RegionListView.as_view(), name='region_list'), path('region/create/', views.RegionCreateView.as_view(), name='region_create'), diff --git a/apps/location/views.py b/apps/location/views.py index b7baa59e..3fba32c2 100644 --- a/apps/location/views.py +++ b/apps/location/views.py @@ -1,14 +1,20 @@ +"""Location app views.""" from rest_framework import generics from rest_framework import permissions - from location import models, serializers +from utils.views import JWTGenericViewMixin # Mixins -class CountryViewMixin(generics.GenericAPIView): +class CountryViewMixin(JWTGenericViewMixin, generics.GenericAPIView): """View Mixin for model Country""" - model = models.Country - queryset = models.Country.objects.all() + + serializer_class = serializers.CountrySerializer + permission_classes = (permissions.AllowAny, ) + + def get_queryset(self): + return models.Country.objects.annotate_localized_fields( + locale=self._get_locale(request=self.request)) class RegionViewMixin(generics.GenericAPIView): @@ -30,30 +36,14 @@ class AddressViewMixin(generics.GenericAPIView): # Country -class CountryCreateView(CountryViewMixin, generics.CreateAPIView): - """Create view for model Country""" - serializer_class = serializers.CountrySerializer +class CountryListView(CountryViewMixin, generics.ListAPIView): + """List view for model Country.""" + + pagination_class = None class CountryRetrieveView(CountryViewMixin, generics.RetrieveAPIView): - """Retrieve view for model Country""" - serializer_class = serializers.CountrySerializer - - -class CountryListView(CountryViewMixin, generics.ListAPIView): - """List view for model Country""" - permission_classes = (permissions.AllowAny, ) - serializer_class = serializers.CountrySerializer - - -class CountryDestroyView(CountryViewMixin, generics.DestroyAPIView): - """Destroy view for model Country""" - serializer_class = serializers.CountrySerializer - - -class CountryUpdateView(CountryViewMixin, generics.UpdateAPIView): - """Update view for model Country""" - serializer_class = serializers.CountrySerializer + """Retrieve view for model Country.""" # Region diff --git a/apps/main/methods.py b/apps/main/methods.py new file mode 100644 index 00000000..99d8fc9b --- /dev/null +++ b/apps/main/methods.py @@ -0,0 +1,61 @@ +"""Main app methods.""" +import logging +from django.conf import settings +from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception +from main import models + + +logger = logging.getLogger(__name__) + + +def site_url(schema, subdomain, domain): + return f'{schema}://{subdomain}.{domain}' + + +def get_user_ip(request): + """Get user ip from request.""" + META = request.META + x_forwarded_for = META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = META.get('REMOTE_ADDR') + return ip + + +def determine_country_code(ip_addr): + """Determine country code.""" + country_code = None + if ip_addr: + try: + geoip = GeoIP2() + country_code = geoip.country_code(ip_addr) + country_code = country_code.lower() + except GeoIP2Exception as ex: + logger.error(f'GEOIP Exception: {ex}') + except Exception as ex: + logger.error(f'GEOIP Base exception: {ex}') + return country_code + + +def determine_user_site_url(country_code): + """Determine user's site url.""" + try: + if country_code is None: + raise ValueError + # search localized site + site = models.SiteSettings.objects.filter( + country__code=country_code).first() + # search default site + if not site: + site = models.SiteSettings.objects.filter(default_site=True).first() + if not site: + raise ValueError + except Exception: + return site_url(schema=settings.SCHEMA_URI, + subdomain=settings.DEFAULT_SUBDOMAIN, + domain=settings.DOMAIN_URI) + else: + return site.site_url + + diff --git a/apps/main/migrations/0003_auto_20190822_0726.py b/apps/main/migrations/0003_auto_20190822_0726.py new file mode 100644 index 00000000..b61e29db --- /dev/null +++ b/apps/main/migrations/0003_auto_20190822_0726.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-22 07:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0002_auto_20190821_0830'), + ] + + operations = [ + migrations.RenameField( + model_name='sitesettings', + old_name='country_code', + new_name='subdomain', + ), + ] diff --git a/apps/main/migrations/0004_auto_20190822_0726.py b/apps/main/migrations/0004_auto_20190822_0726.py new file mode 100644 index 00000000..314394a4 --- /dev/null +++ b/apps/main/migrations/0004_auto_20190822_0726.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-22 07:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0003_auto_20190822_0726'), + ] + + operations = [ + migrations.AlterField( + model_name='sitesettings', + name='subdomain', + field=models.CharField(db_index=True, max_length=255, verbose_name='Subdomain'), + ), + ] diff --git a/apps/main/migrations/0005_auto_20190822_0727.py b/apps/main/migrations/0005_auto_20190822_0727.py new file mode 100644 index 00000000..0b439c69 --- /dev/null +++ b/apps/main/migrations/0005_auto_20190822_0727.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.4 on 2019-08-22 07:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0004_auto_20190822_0710'), + ('main', '0004_auto_20190822_0726'), + ] + + operations = [ + migrations.AddField( + model_name='sitesettings', + name='country', + field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='Country'), + ), + migrations.AddField( + model_name='sitesettings', + name='default_site', + field=models.BooleanField(default=False, verbose_name='Default site'), + ), + ] diff --git a/apps/main/migrations/0006_auto_20190822_1118.py b/apps/main/migrations/0006_auto_20190822_1118.py new file mode 100644 index 00000000..11a10fcf --- /dev/null +++ b/apps/main/migrations/0006_auto_20190822_1118.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-22 11:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0005_auto_20190822_0727'), + ] + + operations = [ + migrations.AlterField( + model_name='sitesettings', + name='subdomain', + field=models.CharField(db_index=True, max_length=255, unique=True, verbose_name='Subdomain'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index 7ace75ae..2f1bdae9 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -1,7 +1,10 @@ """Main app models.""" +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from django.contrib.postgres.fields import JSONField +from main import methods +from location.models import Country from utils.models import ProjectBaseMixin # # @@ -92,8 +95,13 @@ from utils.models import ProjectBaseMixin class SiteSettings(ProjectBaseMixin): # todo: mb foreign key? - country_code = models.CharField(max_length=255, db_index=True, - verbose_name=_('Country code')) + subdomain = models.CharField(max_length=255, db_index=True, unique=True, + verbose_name=_('Subdomain')) + country = models.OneToOneField(Country, on_delete=models.PROTECT, + null=True, blank=True, default=None, + verbose_name=_('Country')) + default_site = models.BooleanField(default=False, + verbose_name=_('Default site')) pinterest_page_url = models.URLField(blank=True, null=True, default=None, verbose_name=_('Pinterest page URL')) twitter_page_url = models.URLField(blank=True, null=True, default=None, @@ -116,13 +124,19 @@ class SiteSettings(ProjectBaseMixin): verbose_name_plural = _('Site settings') def __str__(self): - return f'Site: "{self.country_code}"' + return f'ID: "{self.id}". Site: "{self.site_url}"' @property def published_features(self): return self.feature_set.filter(sitefeature__site_settings=self, sitefeature__published=True) + @property + def site_url(self): + return methods.site_url(schema=settings.SCHEMA_URI, + subdomain=self.subdomain, + domain=settings.DOMAIN_URI) + class Feature(ProjectBaseMixin): """Feature model.""" diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 7cec6747..708bc33c 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -21,12 +21,16 @@ class SiteSettingsSerializer(serializers.ModelSerializer): published_features = FeatureSerializer(many=True, allow_null=None) + #todo: remove this + country_code = serializers.CharField(source='subdomain', read_only=True) + class Meta: """Meta class.""" model = models.SiteSettings fields = ( 'country_code', + 'subdomain', 'pinterest_page_url', 'twitter_page_url', 'facebook_page_url', diff --git a/apps/main/urls.py b/apps/main/urls.py index 6abeeabf..10379c3a 100644 --- a/apps/main/urls.py +++ b/apps/main/urls.py @@ -7,14 +7,14 @@ app = 'main' urlpatterns = [ path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'), - path('site-settings//', views.SiteSettingsView.as_view(), + path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings', ), - path('features/', views.FeaturesLCView.as_view(), - name='features-lc'), - path('features//', views.FeaturesRUDView.as_view(), - name='features-rud'), - path('site-features/', views.SiteFeaturesLCView.as_view(), - name='site-features-lc'), - path('site-features//', views.SiteFeaturesRUDView.as_view(), - name='site-features-rud'), + # path('features/', views.FeaturesLCView.as_view(), + # name='features-lc'), + # path('features//', views.FeaturesRUDView.as_view(), + # name='features-rud'), + # path('site-features/', views.SiteFeaturesLCView.as_view(), + # name='site-features-lc'), + # path('site-features//', views.SiteFeaturesRUDView.as_view(), + # name='site-features-rud'), ] \ No newline at end of file diff --git a/apps/main/views.py b/apps/main/views.py index aa076665..504ef910 100644 --- a/apps/main/views.py +++ b/apps/main/views.py @@ -1,53 +1,7 @@ """Main app views.""" from django.http.response import HttpResponseRedirect from rest_framework import generics, permissions -from main import models, serializers - - -class SiteSettingsView(generics.RetrieveAPIView): - """Site settings View.""" - - lookup_field = 'country_code' - permission_classes = (permissions.AllowAny,) - queryset = models.SiteSettings.objects.all() - serializer_class = serializers.SiteSettingsSerializer - - -class FeatureViewMixin: - """Feature view mixin.""" - - queryset = models.Feature.objects.all() - serializer_class = serializers.FeatureSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - - -class FeaturesLCView(FeatureViewMixin, generics.ListCreateAPIView): - """Site features LC View.""" - - pagination_class = None - - -class FeaturesRUDView(FeatureViewMixin, generics.RetrieveUpdateDestroyAPIView): - """Site features RUD View.""" - - -class SiteFeaturesViewMixin: - """Site feature view mixin.""" - - queryset = models.SiteFeature.objects.all() - serializer_class = serializers.SiteFeatureSerializer - permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - - -class SiteFeaturesLCView(SiteFeaturesViewMixin, generics.ListCreateAPIView): - """Site features LC.""" - - pagination_class = None - - -class SiteFeaturesRUDView(SiteFeaturesViewMixin, - generics.RetrieveUpdateDestroyAPIView): - """Site features RUD.""" +from main import methods, models, serializers class DetermineSiteView(generics.GenericAPIView): @@ -56,4 +10,53 @@ class DetermineSiteView(generics.GenericAPIView): permission_classes = (permissions.AllowAny,) def get(self, request, *args, **kwargs): - return HttpResponseRedirect('http://ru.gm.id-east.ru/') + user_ip = methods.get_user_ip(request) + country_code = methods.determine_country_code(user_ip) + url = methods.determine_user_site_url(country_code) + return HttpResponseRedirect(url) + + +class SiteSettingsView(generics.RetrieveAPIView): + """Site settings View.""" + + lookup_field = 'subdomain' + permission_classes = (permissions.AllowAny,) + queryset = models.SiteSettings.objects.all() + serializer_class = serializers.SiteSettingsSerializer +# +# +# class FeatureViewMixin: +# """Feature view mixin.""" +# +# queryset = models.Feature.objects.all() +# serializer_class = serializers.FeatureSerializer +# permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +# +# +# class FeaturesLCView(FeatureViewMixin, generics.ListCreateAPIView): +# """Site features LC View.""" +# +# pagination_class = None +# +# +# class FeaturesRUDView(FeatureViewMixin, generics.RetrieveUpdateDestroyAPIView): +# """Site features RUD View.""" +# +# +# class SiteFeaturesViewMixin: +# """Site feature view mixin.""" +# +# queryset = models.SiteFeature.objects.all() +# serializer_class = serializers.SiteFeatureSerializer +# permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +# +# +# class SiteFeaturesLCView(SiteFeaturesViewMixin, generics.ListCreateAPIView): +# """Site features LC.""" +# +# pagination_class = None +# +# +# class SiteFeaturesRUDView(SiteFeaturesViewMixin, +# generics.RetrieveUpdateDestroyAPIView): +# """Site features RUD.""" diff --git a/apps/translation/views.py b/apps/translation/views.py index e3b90673..9ddf9983 100644 --- a/apps/translation/views.py +++ b/apps/translation/views.py @@ -33,6 +33,7 @@ class SiteInterfaceDictionaryMixin: return models.SiteInterfaceDictionary.objects.annotate_localized_fields( locale=locale) + class SiteInterfaceDictionaryView(JWTGenericViewMixin, SiteInterfaceDictionaryMixin, generics.ListCreateAPIView): diff --git a/apps/utils/methods.py b/apps/utils/methods.py index f55b0fe2..e0f9325a 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -1,10 +1,10 @@ """Utils app method.""" import random -from django.http.request import HttpRequest -from rest_framework.request import Request import re -from django.utils.timezone import datetime from django.conf import settings +from django.http.request import HttpRequest +from django.utils.timezone import datetime +from rest_framework.request import Request def generate_code(digits=6, string_output=True): diff --git a/project/settings/development.py b/project/settings/development.py index 522fe847..f9809cfe 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -6,4 +6,6 @@ ALLOWED_HOSTS = ['gm.id-east.ru', ] SEND_SMS = False SMS_CODE_SHOW = True +SCHEMA_URI = 'http' +DEFAULT_SUBDOMAIN = 'www' DOMAIN_URI = 'gm.id-east.ru' diff --git a/project/settings/local.py b/project/settings/local.py index 53e14a3b..d05f81eb 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -7,7 +7,9 @@ SEND_SMS = False SMS_CODE_SHOW = True USE_CELERY = False -DOMAIN_URI = '0.0.0.0:8000' +SCHEMA_URI = 'http' +DEFAULT_SUBDOMAIN = 'www' +DOMAIN_URI = 'testserver.com:8000' # OTHER SETTINGS API_HOST = '0.0.0.0:8000'