diff --git a/apps/establishment/models.py b/apps/establishment/models.py index aed78d9f..9334e4b7 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -93,15 +93,20 @@ class EstablishmentQuerySet(models.QuerySet): """ return self.filter(is_publish=True) - def annotate_distance(self, point: Point): + def has_published_reviews(self): + """ + Return QuerySet establishments with published reviews. + """ + return self.filter(reviews__status=Review.READY,) + + def annotate_distance(self, point: Point = None): """ Return QuerySet with annotated field - distance Description: """ - return self.annotate(distance=models.Value( - DistanceMeasure(Distance('address__coordinates', point, srid=4236)).m, - output_field=models.FloatField())) + return self.annotate(distance=Distance('address__coordinates', point, + srid=settings.GEO_DEFAULT_SRID)) def annotate_intermediate_public_mark(self): """ @@ -110,8 +115,8 @@ class EstablishmentQuerySet(models.QuerySet): If establishments in collection POP and its mark is null, then intermediate_mark is set to 10; """ - return self.annotate(intermediate_public_mark=models.Case( - models.When( + return self.annotate(intermediate_public_mark=Case( + When( collections__collection_type=Collection.POP, public_mark__isnull=True, then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK @@ -125,8 +130,8 @@ class EstablishmentQuerySet(models.QuerySet): Description: Similarity mark determined by comparison with compared establishment mark """ - return self.annotate(mark_similarity=models.ExpressionWrapper( - mark - models.F('intermediate_public_mark'), + return self.annotate(mark_similarity=ExpressionWrapper( + mark - F('intermediate_public_mark'), output_field=models.FloatField() )) @@ -154,6 +159,21 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() + def last_reviewed(self, point: Point): + """ + Return QuerySet with last reviewed establishments. + :param point: location Point object, needs to ordering + """ + subquery_filter_by_distance = Subquery( + self.filter(image_url__isnull=False, public_mark__gte=10) + .has_published_reviews() + .annotate_distance(point=point) + .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] + .values('id') + ) + return self.filter(id__in=subquery_filter_by_distance) \ + .order_by('-reviews__published_at') + def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( @@ -169,8 +189,8 @@ class EstablishmentQuerySet(models.QuerySet): favorite_establishments = user.favorites.by_content_type(app_label='establishment', model='establishment') \ .values_list('object_id', flat=True) - return self.annotate(in_favorites=models.Case( - models.When( + return self.annotate(in_favorites=Case( + When( id__in=favorite_establishments, then=True), default=False, @@ -194,7 +214,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): name = models.CharField(_('name'), max_length=255, default='') name_translated = models.CharField(_('Transliterated name'), - max_length=255, default='') + max_length=255, default='') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') @@ -308,6 +328,19 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): 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""" + return self.reviews.published()\ + .order_by('-published_at').first() + + @property + def location(self): + """ + Return Point object of establishment location + """ + return self.address.coordinates + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index bd53c7eb..c1161e92 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -8,6 +8,8 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), + path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), + name='recent-reviews'), path('/', views.EstablishmentRetrieveView.as_view(), name='detail'), path('/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), diff --git a/apps/establishment/views/common.py b/apps/establishment/views/common.py index b57d6ef6..922882da 100644 --- a/apps/establishment/views/common.py +++ b/apps/establishment/views/common.py @@ -11,6 +11,6 @@ class EstablishmentMixin: permission_classes = (permissions.AllowAny,) def get_queryset(self): - """Overrided method 'get_queryset'.""" + """Overridden method 'get_queryset'.""" return models.Establishment.objects.published() \ .prefetch_actual_employees() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 62b12016..22ef3abc 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -27,6 +27,25 @@ class EstablishmentListView(EstablishmentMixin, generics.ListAPIView): .annotate_in_favorites(user=self.request.user) +class EstablishmentRecentReviewListView(EstablishmentListView): + """List view for last reviewed establishments.""" + pagination_class = EstablishmentPortionPagination + + def get_queryset(self): + """Overridden method 'get_queryset'.""" + qs = super().get_queryset() + user_ip = methods.get_user_ip(self.request) + query_params = self.request.query_params + if 'longitude' in query_params and 'latitude' in query_params: + longitude, latitude = query_params.get('longitude'), query_params.get('latitude') + else: + longitude, latitude = methods.determine_coordinates(user_ip) + if not longitude or not latitude: + return qs.none() + point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID) + return qs.last_reviewed(point=point) + + class EstablishmentSimilarListView(EstablishmentListView): """Resource for getting a list of establishments.""" serializer_class = serializers.EstablishmentListSerializer diff --git a/project/settings/base.py b/project/settings/base.py index 10eb3544..cfea18a5 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -327,15 +327,30 @@ FCM_DJANGO_SETTINGS = { # Thumbnail settings THUMBNAIL_ALIASES = { - '': { - 'tiny': {'size': (100, 0), }, - 'small': {'size': (480, 0), }, - 'middle': {'size': (700, 0), }, - 'large': {'size': (1500, 0), }, - 'default': {'size': (300, 200), 'crop': True}, - 'gallery': {'size': (240, 160), 'crop': True}, - 'establishment_preview': {'size': (300, 280), 'crop': True}, - } + 'news_preview': { + 'web': {'size': (300, 260), } + }, + 'news_promo_horizontal': { + 'web': {'size': (1900, 600), }, + 'mobile': {'size': (375, 260), }, + }, + 'news_tile_horizontal': { + 'web': {'size': (300, 275), }, + 'mobile': {'size': (343, 180), }, + }, + 'news_tile_vertical': { + 'web': {'size': (300, 380), }, + }, + 'news_highlight_vertical': { + 'web': {'size': (460, 630), }, + }, + 'news_editor': { + 'web': {'size': (940, 430), }, # при загрузке через контент эдитор + 'mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe + }, + 'avatar_comments': { + 'web': {'size': (116, 116), }, + }, } # Password reset