diff --git a/apps/account/migrations/0011_merge_20191011_1336.py b/apps/account/migrations/0011_merge_20191011_1336.py new file mode 100644 index 00000000..6a031068 --- /dev/null +++ b/apps/account/migrations/0011_merge_20191011_1336.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-11 13:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191002_0648'), + ('account', '0010_user_password_confirmed'), + ] + + operations = [ + ] diff --git a/apps/account/migrations/0012_merge_20191015_0912.py b/apps/account/migrations/0012_merge_20191015_0912.py new file mode 100644 index 00000000..558940ce --- /dev/null +++ b/apps/account/migrations/0012_merge_20191015_0912.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 09:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0011_merge_20191014_1258'), + ('account', '0011_merge_20191011_1336'), + ] + + operations = [ + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 0600c477..13adfba0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,6 +1,8 @@ """Establishment models.""" +from datetime import datetime from functools import reduce +import elasticsearch_dsl from django.conf import settings from django.contrib.contenttypes import fields as generic from django.contrib.gis.db.models.functions import Distance @@ -12,6 +14,7 @@ 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 py_tz from collection.models import Collection from location.models import Address @@ -128,15 +131,15 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() - # def es_search(self, value, locale=None): - # """Search text via ElasticSearch.""" - # from search_indexes.documents import EstablishmentDocument - # search = EstablishmentDocument.search().filter( - # Elastic_Q('match', name=value) | - # Elastic_Q('match', **{f'description.{locale}': value}) - # ).execute() - # ids = [result.meta.id for result in search] - # return self.filter(id__in=ids) + def es_search(self, value, locale=None): + """Search text via ElasticSearch.""" + from search_indexes.documents import EstablishmentDocument + search = EstablishmentDocument.search().filter( + elasticsearch_dsl.Q('match', name=value) | + elasticsearch_dsl.Q('match', **{f'description.{locale}': value}) + ).execute() + ids = [result.meta.id for result in search] + return self.filter(id__in=ids) def by_country_code(self, code): """Return establishments by country code""" @@ -416,6 +419,32 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): def best_price_carte(self): return 200 + @property + def works_noon(self): + """ Used for indexing working by day """ + return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon] + + @property + def works_evening(self): + """ Used for indexing working by day """ + return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon] + + # @property + def works_now(self): + """ Is establishment working now """ + now_at_est_tz = datetime.now(tz=py_tz(self.address.tz_name)) + current_week = now_at_est_tz.weekday() + schedule_for_today = self.schedule.filter(weekday=current_week).first() + if schedule_for_today is None: + return False + time_at_est_tz = now_at_est_tz.time() + return schedule_for_today.closed_at > time_at_est_tz > schedule_for_today.opening_at + + @property + def tags_indexing(self): + return [{'id': tag.metadata.id, + 'label': tag.metadata.label} for tag in self.tags.all()] + @property def last_published_review(self): """Return last published review""" @@ -431,8 +460,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def the_most_recent_award(self): - return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( - field_name='vintage_year') + return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \ + .latest(field_name='vintage_year') @property def country_id(self): diff --git a/apps/location/models.py b/apps/location/models.py index c12f7ff0..3591d4df 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -5,8 +5,10 @@ from django.db.models.signals import post_save from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ -from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField + +from timezonefinder import TimezoneFinder from translation.models import Language +from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): @@ -112,6 +114,11 @@ class Address(models.Model): def get_street_name(self): return self.street_name_1 or self.street_name_2 + @property + def tz_name(self): + tf = TimezoneFinder(in_memory=True) + return tf.certain_timezone_at(lng=self.latitude, lat=self.longitude) + @property def latitude(self): return self.coordinates.y if self.coordinates else float(0) diff --git a/apps/news/migrations/0014_auto_20190926_1156.py b/apps/news/migrations/0014_auto_20190926_1156.py deleted file mode 100644 index d16f0ad3..00000000 --- a/apps/news/migrations/0014_auto_20190926_1156.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-26 11:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0013_auto_20190924_0806'), - ] - - operations = [ - migrations.RemoveField( - model_name='news', - name='image_url', - ), - migrations.RemoveField( - model_name='news', - name='preview_image_url', - ), - ] diff --git a/apps/news/migrations/0015_newsgallery.py b/apps/news/migrations/0015_newsgallery.py index a8422dab..8f81b4f8 100644 --- a/apps/news/migrations/0015_newsgallery.py +++ b/apps/news/migrations/0015_newsgallery.py @@ -8,7 +8,6 @@ class Migration(migrations.Migration): dependencies = [ ('gallery', '0002_auto_20190930_0714'), - ('news', '0014_auto_20190926_1156'), ] operations = [ diff --git a/apps/news/migrations/0022_merge_20191015_0912.py b/apps/news/migrations/0022_merge_20191015_0912.py new file mode 100644 index 00000000..08b5a613 --- /dev/null +++ b/apps/news/migrations/0022_merge_20191015_0912.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 09:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0021_auto_20191009_1408'), + ('news', '0021_merge_20191002_1300'), + ] + + operations = [ + ] diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 12c76e67..417db72e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -47,6 +47,7 @@ class NewsBannerSerializer(ProjectModelSerializer): class CropImageSerializer(serializers.Serializer): """Serializer for crop images for News object.""" + preview_url = serializers.SerializerMethodField() promo_horizontal_web_url = serializers.SerializerMethodField() promo_horizontal_mobile_url = serializers.SerializerMethodField() diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index c30d4c58..5d858321 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -32,6 +32,13 @@ class EstablishmentDocument(Document): }), }, multi=True) + works_evening = fields.ListField(fields.IntegerField( + attr='works_evening' + )) + works_noon = fields.ListField(fields.IntegerField( + attr='works_noon' + )) + works_now = fields.BooleanField(attr='works_now') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), @@ -39,6 +46,14 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES), }, multi=True) + schedule = fields.ListField(fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'weekday': fields.IntegerField(attr='weekday'), + 'weekday_display': fields.KeywordField(attr='get_weekday_display'), + 'closed_at': fields.KeywordField(attr='closed_at_str'), + } + )) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index cd6fc089..21c59e68 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,6 +24,7 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') + preview_image_url = fields.TextField(attr='preview_image_url') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 18a1e240..535974a9 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -30,6 +30,15 @@ class AddressDocumentSerializer(serializers.Serializer): geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') +class ScheduleDocumentSerializer(serializers.Serializer): + """Schedule serializer for ES Document""" + + id = serializers.IntegerField() + weekday = serializers.IntegerField() + weekday_display = serializers.CharField() + closed_at = serializers.CharField() + + class NewsDocumentSerializer(DocumentSerializer): """News document serializer.""" @@ -68,6 +77,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): address = AddressDocumentSerializer() tags = TagsDocumentSerializer(many=True) + schedule = ScheduleDocumentSerializer(many=True, allow_null=True) class Meta: """Meta class.""" @@ -84,6 +94,10 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'preview_image', 'address', 'tags', + 'schedule', + 'works_noon', + 'works_evening', + 'works_now', # 'collections', # 'establishment_type', # 'establishment_subtypes', diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 50c32fc7..913c2d95 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -111,6 +111,9 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): }, 'tags_category_id': { 'field': 'tags.category.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], }, 'collection_type': { 'field': 'collections.collection_type' @@ -121,6 +124,25 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'establishment_subtypes': { 'field': 'establishment_subtypes.id' }, + 'works_noon': { + 'field': 'works_noon', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, + 'works_evening': { + 'field': 'works_evening', + 'lookups': [ + constants.LOOKUP_FILTER_TERM, + ], + }, + 'works_now': { + 'field': 'works_now', + 'lookups': [ + constants.LOOKUP_FILTER_EXISTS, + constants.LOOKUP_QUERY_IN, + ] + }, } geo_spatial_filter_fields = { diff --git a/apps/tag/models.py b/apps/tag/models.py index 44eacddc..f5565e9f 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -56,7 +56,7 @@ class TagCategoryQuerySet(models.QuerySet): def with_tags(self, switcher=True): """Filter by existing tags.""" - return self.filter(tags__isnull=not switcher) + return self.exclude(tags__isnull=switcher) class TagCategory(TranslatedFieldsMixin, models.Model): diff --git a/apps/tag/urls/mobile.py b/apps/tag/urls/mobile.py new file mode 100644 index 00000000..561697d2 --- /dev/null +++ b/apps/tag/urls/mobile.py @@ -0,0 +1,7 @@ +from tag.urls.web import urlpatterns as common_urlpatterns + +urlpatterns = [ + +] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/timetable/models.py b/apps/timetable/models.py index 53670d02..35469c32 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from datetime import time from utils.models import ProjectBaseMixin @@ -14,6 +15,8 @@ class Timetable(ProjectBaseMixin): SATURDAY = 5 SUNDAY = 6 + NOON = time(17, 0) + WEEKDAYS_CHOICES = ( (MONDAY, _('Monday')), (TUESDAY, _('Tuesday')), @@ -32,6 +35,18 @@ class Timetable(ProjectBaseMixin): opening_at = models.TimeField(verbose_name=_('Opening time'), null=True) closed_at = models.TimeField(verbose_name=_('Closed time'), null=True) + @property + def closed_at_str(self): + return str(self.closed_at) if self.closed_at else None + + @property + def works_at_noon(self): + return bool(self.closed_at and self.closed_at <= self.NOON) + + @property + def works_at_afternoon(self): + return bool(self.closed_at and self.closed_at > self.NOON) + class Meta: """Meta class.""" verbose_name = _('Timetable') diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index 1339cc8e..76a37257 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -77,3 +77,16 @@ class ScheduleCreateSerializer(ScheduleRUDSerializer): schedule_qs.delete() establishment.schedule.add(instance) return instance + +class TimetableSerializer(serializers.ModelSerializer): + """Serailzier for Timetable model.""" + weekday_display = serializers.CharField(source='get_weekday_display', + read_only=True) + + class Meta: + model = Timetable + fields = ( + 'id', + 'weekday_display', + 'works_at_noon', + ) diff --git a/apps/timetable/urls/__init__.py b/apps/timetable/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/timetable/urls.py b/apps/timetable/urls/common.py similarity index 67% rename from apps/timetable/urls.py rename to apps/timetable/urls/common.py index ff4f4f00..95593b20 100644 --- a/apps/timetable/urls.py +++ b/apps/timetable/urls/common.py @@ -6,5 +6,5 @@ from timetable import views app_name = 'timetable' urlpatterns = [ - path('', views.TimetableListView.as_view(), name='list') + # path('', views.TimetableListView.as_view(), name='list') ] \ No newline at end of file diff --git a/apps/timetable/urls/mobile.py b/apps/timetable/urls/mobile.py new file mode 100644 index 00000000..1c6419a3 --- /dev/null +++ b/apps/timetable/urls/mobile.py @@ -0,0 +1,6 @@ +from timetable.urls.common import urlpatterns as common_urlpatterns + + +urlpatterns = [] + +urlpatterns.extend(common_urlpatterns) \ No newline at end of file diff --git a/apps/timetable/urls/web.py b/apps/timetable/urls/web.py new file mode 100644 index 00000000..1c6419a3 --- /dev/null +++ b/apps/timetable/urls/web.py @@ -0,0 +1,6 @@ +from timetable.urls.common import urlpatterns as common_urlpatterns + + +urlpatterns = [] + +urlpatterns.extend(common_urlpatterns) \ No newline at end of file diff --git a/apps/timetable/views.py b/apps/timetable/views.py index d8a13f71..5129f068 100644 --- a/apps/timetable/views.py +++ b/apps/timetable/views.py @@ -1,4 +1,4 @@ -from rest_framework import generics +from rest_framework import generics, permissions from timetable import serialziers, models @@ -6,3 +6,5 @@ class TimetableListView(generics.ListAPIView): """Method to get timetables""" serializer_class = serialziers.TimetableSerializer queryset = models.Timetable.objects.all() + pagination_class = None + permission_classes = (permissions.AllowAny, ) diff --git a/celerybeat-schedule b/celerybeat-schedule index 3efe528a..e1a56a15 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ diff --git a/project/settings/amazon_s3.py b/project/settings/amazon_s3.py index 73b15e28..c793dd77 100644 --- a/project/settings/amazon_s3.py +++ b/project/settings/amazon_s3.py @@ -1,13 +1,16 @@ """Settings for Amazon S3""" import os + from .base import MEDIA_LOCATION # AMAZON S3 +AWS_S3_REGION_NAME = 'eu-central-1' AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY') AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME') -AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com' +AWS_S3_CUSTOM_DOMAIN = f's3.{AWS_S3_REGION_NAME}.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}' AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'} +AWS_S3_ADDRESSING_STYLE = 'path' # Static settings # PUBLIC_STATIC_LOCATION = 'static' diff --git a/project/settings/base.py b/project/settings/base.py index cd0a63b0..fff8789b 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -96,6 +96,7 @@ EXTERNAL_APPS = [ 'phonenumber_field', 'storages', 'sorl.thumbnail', + 'timezonefinder' ] diff --git a/project/settings/production.py b/project/settings/production.py index 00c27a85..b7ddc401 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -7,4 +7,4 @@ GUESTONLINE_SERVICE = 'https://api.guestonline.fr/' GUESTONLINE_TOKEN = '' LASTABLE_SERVICE = '' LASTABLE_TOKEN = '' -LASTABLE_PROXY = '' \ No newline at end of file +LASTABLE_PROXY = '' diff --git a/project/settings/stage.py b/project/settings/stage.py index e1443ab1..49a7ae0f 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -6,7 +6,7 @@ ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126'] SEND_SMS = False SMS_CODE_SHOW = True -USE_CELERY = False +USE_CELERY = True SCHEMA_URI = 'https' DEFAULT_SUBDOMAIN = 'www' diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index d14bd898..6fe14060 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -12,7 +12,6 @@ -