Merge branch 'develop' into account_test

This commit is contained in:
Виктор Гладких 2019-09-23 16:20:00 +03:00
commit b57eb29b1c
36 changed files with 834 additions and 198 deletions

View File

@ -1,5 +1,6 @@
"""Common account serializers""" """Common account serializers"""
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import password_validation as password_validators from django.contrib.auth import password_validation as password_validators
from fcm_django.models import FCMDevice from fcm_django.models import FCMDevice
from rest_framework import exceptions from rest_framework import exceptions
@ -80,23 +81,31 @@ class ChangePasswordSerializer(serializers.ModelSerializer):
"""Serializer for model User.""" """Serializer for model User."""
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
old_password = serializers.CharField(write_only=True)
class Meta: class Meta:
"""Meta class""" """Meta class"""
model = models.User model = models.User
fields = ('password', ) fields = (
'password',
'old_password',
)
def validate(self, attrs): def validate(self, attrs):
"""Override validate method""" """Override validate method"""
password = attrs.get('password') password = attrs.get('password')
old_password = attrs.get('old_password')
try: try:
# Check old password
if not self.instance.check_password(raw_password=old_password):
raise serializers.ValidationError(_('Old password mismatch.'))
# Compare new password with the old ones # Compare new password with the old ones
if self.instance.check_password(raw_password=password): if self.instance.check_password(raw_password=password):
raise utils_exceptions.PasswordsAreEqual() raise serializers.ValidationError(_('Password is already in use'))
# Validate password # Validate password
password_validators.validate_password(password=password) password_validators.validate_password(password=password)
except serializers.ValidationError as e: except serializers.ValidationError as e:
raise serializers.ValidationError(str(e)) raise serializers.ValidationError({'detail': e.detail})
else: else:
return attrs return attrs

View File

@ -144,7 +144,8 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
return self._put_cookies_in_response( return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token, cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token), refresh_token=refresh_token,
permanent=True),
response=response) response=response)

View File

@ -91,7 +91,9 @@ class GuideQuerySet(models.QuerySet):
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""Guide model.""" """Guide model."""
parent = models.ForeignKey( parent = models.ForeignKey(
'self', verbose_name=_('parent'), on_delete=models.CASCADE) 'self', verbose_name=_('parent'), on_delete=models.CASCADE,
null=True, blank=True, default=None
)
advertorials = JSONField( advertorials = JSONField(
_('advertorials'), null=True, blank=True, _('advertorials'), null=True, blank=True,
default=None, help_text='{"key":"value"}') default=None, help_text='{"key":"value"}')

View File

@ -1,3 +1,82 @@
from django.test import TestCase import json, pytz
from datetime import datetime
from rest_framework.test import APITestCase
from account.models import User
from rest_framework import status
from http.cookies import SimpleCookie
from collection.models import Collection, Guide
from location.models import Country
# Create your tests here. # Create your tests here.
class BaseTestCase(APITestCase):
def setUp(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.newsletter = True
self.user = User.objects.create_user(
username=self.username, email=self.email, password=self.password)
#get tokkens
tokkens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie(
{'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token'),
'country_code': 'en'})
class CollectionListTests(BaseTestCase):
def test_collection_list_Read(self):
response = self.client.get('/api/web/collections/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionDetailTests(BaseTestCase):
def setUp(self):
super().setUp()
country = Country.objects.first()
if not country:
country = Country.objects.create(
name=json.dumps({"en-GB": "Test country"}),
code="en"
)
self.collection = Collection.objects.create(
name='Test collection',
is_publish=True,
start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc),
country=country
)
def test_collection_detail_Read(self):
response = self.client.get(f'/api/web/collections/{self.collection.id}/establishments/?country_code=en',
format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionGuideTests(CollectionDetailTests):
def test_guide_list_Read(self):
response = self.client.get('/api/web/collections/guides/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class CollectionGuideDetailTests(CollectionDetailTests):
def setUp(self):
super().setUp()
self.guide = Guide.objects.create(
collection=self.collection,
start=datetime.now(pytz.utc),
end=datetime.now(pytz.utc)
)
def test_guide_detail_Read(self):
response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-09-23 09:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('establishment', '0028_auto_20190920_1205'),
]
operations = [
migrations.AddField(
model_name='establishment',
name='name_transliterated',
field=models.CharField(default='', max_length=255, verbose_name='Transliterated name'),
),
]

View File

@ -219,16 +219,16 @@ class EstablishmentQuerySet(models.QuerySet):
:return: all establishments within the specified radius of specified point :return: all establishments within the specified radius of specified point
:param unit: length unit e.g. m, km. Default is 'm'. :param unit: length unit e.g. m, km. Default is 'm'.
""" """
from django.contrib.gis.measure import Distance
kwargs = {unit: radius} kwargs = {unit: radius}
return self.filter(address__coordinates__distance_lte=(center, Distance(**kwargs))) return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
"""Establishment model.""" """Establishment model."""
name = models.CharField(_('name'), max_length=255, default='') name = models.CharField(_('name'), max_length=255, default='')
name_translated = models.CharField(_('Transliterated name'),
max_length=255, default='')
description = TJSONField(blank=True, null=True, default=None, description = TJSONField(blank=True, null=True, default=None,
verbose_name=_('description'), verbose_name=_('description'),
help_text='{"en-GB":"some text"}') help_text='{"en-GB":"some text"}')

View File

@ -34,7 +34,39 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer):
'type_id', 'type_id',
'type', 'type',
'socials', 'socials',
'image_url' 'image_url',
# TODO: check in admin filters
'is_publish'
]
class EstablishmentRUDSerializer(EstablishmentBaseSerializer):
"""Establishment create serializer"""
type_id = serializers.PrimaryKeyRelatedField(
source='establishment_type',
queryset=models.EstablishmentType.objects.all(), write_only=True
)
phones = ContactPhonesSerializer(read_only=False, many=True, )
emails = ContactEmailsSerializer(read_only=False, many=True, )
socials = SocialNetworkRelatedSerializers(read_only=False, many=True, )
class Meta:
model = models.Establishment
fields = [
'id',
'name',
'website',
'phones',
'emails',
'price_level',
'toque_number',
'type_id',
'type',
'socials',
'image_url',
# TODO: check in admin filters
'is_publish'
] ]

View File

@ -154,6 +154,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'id',
'name', 'name',
'name_transliterated',
'price_level', 'price_level',
'toque_number', 'toque_number',
'public_mark', 'public_mark',

View File

@ -1,8 +1,11 @@
import json
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from account.models import User from account.models import User
from rest_framework import status from rest_framework import status
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from establishment.models import Employee from main.models import Currency
from establishment.models import Establishment, EstablishmentType, Menu
# Create your tests here. # Create your tests here.
@ -21,9 +24,41 @@ class BaseTestCase(APITestCase):
{'access_token': tokkens.get('access_token'), {'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token')}) 'refresh_token': tokkens.get('refresh_token')})
self.establishment_type = EstablishmentType.objects.create(name="Test establishment type")
class EstablishmentTests(BaseTestCase):
def test_establishment_CRUD(self):
response = self.client.get('/api/back/establishments/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'name': 'Test establishment',
'type_id': self.establishment_type.id,
'is_publish': True
}
response = self.client.post('/api/back/establishments/', data=data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
establishment = response.json()
response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'name': 'Test new establishment'
}
response = self.client.patch(f'/api/back/establishments/{establishment["id"]}/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class EmployeeTests(BaseTestCase): class EmployeeTests(BaseTestCase):
def test_employee_CRD(self): def test_employee_CRUD(self):
response = self.client.get('/api/back/establishments/employees/', format='json') response = self.client.get('/api/back/establishments/employees/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -38,9 +73,191 @@ class EmployeeTests(BaseTestCase):
response = self.client.get('/api/back/establishments/employees/1/', format='json') response = self.client.get('/api/back/establishments/employees/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'name': 'Test new name'
}
response = self.client.patch('/api/back/establishments/employees/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/employees/1/', format='json') response = self.client.delete('/api/back/establishments/employees/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
# Class to test childs
class ChildTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self.establishment = Establishment.objects.create(
name="Test establishment",
establishment_type_id=self.establishment_type.id,
is_publish=True
)
# Test childs
class EmailTests(ChildTestCase):
def test_email_CRUD(self):
response = self.client.get('/api/back/establishments/emails/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'email': "test@test.com",
'establishment': self.establishment.id
}
response = self.client.post('/api/back/establishments/emails/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/emails/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'email': 'testnew@test.com'
}
response = self.client.patch('/api/back/establishments/emails/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/emails/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class PhoneTests(ChildTestCase):
def test_phone_CRUD(self):
response = self.client.get('/api/back/establishments/phones/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'phone': "+79999999999",
'establishment': self.establishment.id
}
response = self.client.post('/api/back/establishments/phones/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/phones/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'phone': '+79999999998'
}
response = self.client.patch('/api/back/establishments/phones/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/phones/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class SocialTests(ChildTestCase):
def test_social_CRUD(self):
response = self.client.get('/api/back/establishments/socials/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'title': "Test social",
'url': 'https://testsocial.com',
'establishment': self.establishment.id
}
response = self.client.post('/api/back/establishments/socials/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/socials/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'title': 'Test new social'
}
response = self.client.patch('/api/back/establishments/socials/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/socials/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class PlateTests(ChildTestCase):
def test_plate_CRUD(self):
response = self.client.get('/api/back/establishments/plates/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
menu = Menu.objects.create(
category=json.dumps({"en-GB": "Test category"}),
establishment=self.establishment
)
currency = Currency.objects.create(name="Test currency")
data = {
'name': json.dumps({"en-GB": "Test plate"}),
'establishment': self.establishment.id,
'price': 10,
'menu': menu.id,
'currency_id': currency.id
}
response = self.client.post('/api/back/establishments/plates/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/plates/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'name': json.dumps({"en-GB": "Test new plate"})
}
response = self.client.patch('/api/back/establishments/plates/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/plates/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class MenuTests(ChildTestCase):
def test_menu_CRUD(self):
response = self.client.get('/api/back/establishments/menus/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = {
'category': json.dumps({"en-GB": "Test category"}),
'establishment': self.establishment.id
}
response = self.client.post('/api/back/establishments/menus/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get('/api/back/establishments/menus/1/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'category': json.dumps({"en-GB": "Test new category"})
}
response = self.client.patch('/api/back/establishments/menus/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete('/api/back/establishments/menus/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class EstablishmentShedulerTests(ChildTestCase):
def test_shedule_CRUD(self):
data = {
'weekday': 1
}
response = self.client.post(f'/api/back/establishments/{self.establishment.id}/schedule/', data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.get(f'/api/back/establishments/{self.establishment.id}/schedule/1/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
update_data = {
'weekday': 2
}
response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/schedule/1/', data=update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/schedule/1/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

View File

@ -9,7 +9,7 @@ app_name = 'establishment'
urlpatterns = [ urlpatterns = [
path('', views.EstablishmentListCreateView.as_view(), name='list'), path('', views.EstablishmentListCreateView.as_view(), name='list'),
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'), path('<int:pk>/', views.EstablishmentRUDView.as_view(), name='detail'),
path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(), path('<int:pk>/schedule/<int:schedule_id>/', views.EstablishmentScheduleRUDView.as_view(),
name='schedule-rud'), name='schedule-rud'),
path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(), path('<int:pk>/schedule/', views.EstablishmentScheduleCreateView.as_view(),
@ -17,7 +17,7 @@ urlpatterns = [
path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'),
path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'), path('menus/<int:pk>/', views.MenuRUDView.as_view(), name='menu-rud'),
path('plates/', views.PlateListCreateView.as_view(), name='plates'), path('plates/', views.PlateListCreateView.as_view(), name='plates'),
path('plates/<int:pk>/', views.PlateListCreateView.as_view(), name='plate-rud'), path('plates/<int:pk>/', views.PlateRUDView.as_view(), name='plate-rud'),
path('socials/', views.SocialListCreateView.as_view(), name='socials'), path('socials/', views.SocialListCreateView.as_view(), name='socials'),
path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'), path('socials/<int:pk>/', views.SocialRUDView.as_view(), name='social-rud'),
path('phones/', views.PhonesListCreateView.as_view(), name='phones'), path('phones/', views.PhonesListCreateView.as_view(), name='phones'),

View File

@ -13,6 +13,11 @@ class EstablishmentListCreateView(EstablishmentMixin, generics.ListCreateAPIView
serializer_class = serializers.EstablishmentListCreateSerializer serializer_class = serializers.EstablishmentListCreateSerializer
class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView):
queryset = models.Establishment.objects.all()
serializer_class = serializers.EstablishmentRUDSerializer
class MenuListCreateView(generics.ListCreateAPIView): class MenuListCreateView(generics.ListCreateAPIView):
"""Menu list create view.""" """Menu list create view."""
serializer_class = serializers.MenuSerializers serializer_class = serializers.MenuSerializers
@ -83,7 +88,8 @@ class EmployeeListCreateView(generics.ListCreateAPIView):
queryset = models.Employee.objects.all() queryset = models.Employee.objects.all()
pagination_class = None pagination_class = None
class EmployeeRUDView(generics.RetrieveDestroyAPIView):
class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView):
"""Social RUD view.""" """Social RUD view."""
serializer_class = serializers.EmployeeBackSerializers serializer_class = serializers.EmployeeBackSerializers
queryset = models.Employee.objects.all() queryset = models.Employee.objects.all()

View File

@ -1,5 +1,6 @@
"""Establishment app views.""" """Establishment app views."""
from django.contrib.gis.geos import Point
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions from rest_framework import generics, permissions
@ -29,13 +30,8 @@ class EstablishmentSimilarListView(EstablishmentListView):
def get_queryset(self): def get_queryset(self):
"""Override get_queryset method""" """Override get_queryset method"""
qs = super().get_queryset() return super().get_queryset().similar(establishment_pk=self.kwargs.get('pk'))\
if not qs.filter(pk=self.kwargs.get('pk')).exists(): .order_by('-total_mark')[:13]
return qs.none()
return qs.similar(establishment_pk=self.kwargs.get('pk'))\
.order_by('-total_mark')[:13]
class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView): class EstablishmentRetrieveView(EstablishmentMixin, generics.RetrieveAPIView):
"""Resource for getting a establishment.""" """Resource for getting a establishment."""
@ -108,23 +104,25 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D
return obj return obj
class EstablishmentNearestRetrieveView(EstablishmentMixin, generics.ListAPIView): class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
"""Resource for getting list of nearest establishments.""" """Resource for getting list of nearest establishments."""
serializer_class = serializers.EstablishmentListSerializer serializer_class = serializers.EstablishmentListSerializer
filter_class = filters.EstablishmentFilter filter_class = filters.EstablishmentFilter
def get_queryset(self): def get_queryset(self):
"""Overrided method 'get_queryset'.""" """Overridden method 'get_queryset'."""
from django.contrib.gis.geos import Point lat = self.request.query_params.get('lat')
lon = self.request.query_params.get('lon')
radius = self.request.query_params.get('radius')
unit = self.request.query_params.get('unit')
center = Point(float(self.request.query_params["lat"]), float(self.request.query_params["lon"])) qs = super(EstablishmentNearestRetrieveView, self).get_queryset()
radius = float(self.request.query_params["radius"]) if lat and lon and radius and unit:
unit = self.request.query_params.get("unit", None) center = Point(float(lat), float(lon))
by_distance_from_point_kwargs = {"center": center, "radius": radius, "unit": unit} filter_kwargs = {'center': center, 'radius': float(radius), 'unit': unit}
return super(EstablishmentNearestRetrieveView, self).get_queryset() \ return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
.by_distance_from_point(**{k: v for k, v in by_distance_from_point_kwargs.items() if v is not None}) \ if v is not None})
.by_country_code(code=self.request.country_code) \ return qs
.annotate_in_favorites(user=self.request.user)
class EstablishmentTagListView(generics.ListAPIView): class EstablishmentTagListView(generics.ListAPIView):

View File

@ -1,7 +1,9 @@
"""Location app common serializers."""
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from rest_framework import serializers from rest_framework import serializers
from location import models from location import models
from utils.serializers import TranslatedField
class CountrySerializer(serializers.ModelSerializer): class CountrySerializer(serializers.ModelSerializer):
@ -20,6 +22,18 @@ class CountrySerializer(serializers.ModelSerializer):
] ]
class CountrySimpleSerializer(serializers.ModelSerializer):
"""Simple country serializer."""
name_translated = TranslatedField()
class Meta:
"""Meta class."""
model = models.Country
fields = ('id', 'code', 'name_translated')
class RegionSerializer(serializers.ModelSerializer): class RegionSerializer(serializers.ModelSerializer):
"""Region serializer""" """Region serializer"""

View File

@ -1,9 +1,9 @@
"""Main app serializers.""" """Main app serializers."""
from rest_framework import serializers from rest_framework import serializers
from advertisement.serializers.web import AdvertisementSerializer from advertisement.serializers.web import AdvertisementSerializer
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from utils.serializers import TranslatedField
class FeatureSerializer(serializers.ModelSerializer): class FeatureSerializer(serializers.ModelSerializer):
@ -109,16 +109,16 @@ class AwardSerializer(AwardBaseSerializer):
class MetaDataContentSerializer(serializers.ModelSerializer): class MetaDataContentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='metadata.id', read_only=True, ) """MetaData content serializer."""
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True) id = serializers.IntegerField(source='metadata.id', read_only=True)
label_translated = TranslatedField(source='metadata.label_translated')
class Meta: class Meta:
"""Meta class."""
model = models.MetaDataContent model = models.MetaDataContent
fields = [ fields = ('id', 'label_translated')
'id',
'label_translated',
]
class CurrencySerializer(serializers.ModelSerializer): class CurrencySerializer(serializers.ModelSerializer):

View File

@ -0,0 +1,54 @@
# Generated by Django 2.2.4 on 2019-09-23 11:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('news', '0009_auto_20190901_1032'),
]
operations = [
migrations.AddField(
model_name='news',
name='author',
field=models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='Author'),
),
migrations.AddField(
model_name='news',
name='image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
),
migrations.AddField(
model_name='news',
name='preview_image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Preview image URL path'),
),
migrations.AlterField(
model_name='news',
name='address',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address'),
),
migrations.AlterField(
model_name='news',
name='country',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='country'),
),
migrations.AlterField(
model_name='news',
name='end',
field=models.DateTimeField(verbose_name='End'),
),
migrations.AlterField(
model_name='news',
name='news_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='news.NewsType', verbose_name='news type'),
),
migrations.AlterField(
model_name='news',
name='start',
field=models.DateTimeField(verbose_name='Start'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.2.4 on 2019-09-23 11:34
from django.db import migrations
from django.conf import settings
def copy_image_links(apps, schemaeditor):
News = apps.get_model('news', 'News')
for news in News.objects.all():
if news.image:
news.image_url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{news.image.image.url}'
news.save()
class Migration(migrations.Migration):
dependencies = [
('news', '0010_auto_20190923_1131'),
]
operations = [
migrations.RunPython(copy_image_links, migrations.RunPython.noop),
migrations.RemoveField(
model_name='news',
name='image',
),
]

View File

@ -1,25 +1,31 @@
"""News app models.""" """News app models."""
from django.db import models from django.db import models
from django.contrib.contenttypes import fields as generic
from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
class NewsType(models.Model): class NewsType(models.Model):
"""NewsType model.""" """NewsType model."""
name = models.CharField(_('name'), max_length=250) name = models.CharField(_('name'), max_length=250)
class Meta: class Meta:
"""Meta class."""
verbose_name_plural = _('news types') verbose_name_plural = _('news types')
verbose_name = _('news type') verbose_name = _('news type')
def __str__(self): def __str__(self):
"""Overrided __str__ method."""
return self.name return self.name
class NewsQuerySet(models.QuerySet): class NewsQuerySet(models.QuerySet):
"""QuerySet for model News""" """QuerySet for model News"""
def by_type(self, news_type): def by_type(self, news_type):
"""Filter News by type""" """Filter News by type"""
return self.filter(news_type__name=news_type) return self.filter(news_type__name=news_type)
@ -30,48 +36,56 @@ class NewsQuerySet(models.QuerySet):
def published(self): def published(self):
"""Return only published news""" """Return only published news"""
return self.filter(is_publish=True) now = timezone.now()
return self.filter(is_publish=True, start__lte=now, end__gte=now)
def with_related(self):
"""Return qs with related objects."""
return self.select_related('news_type', 'country').prefetch_related('tags')
class News(BaseAttributes, TranslatedFieldsMixin): class News(BaseAttributes, TranslatedFieldsMixin):
"""News model.""" """News model."""
image = models.ForeignKey( news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
'gallery.Image', null=True, blank=True, default=None, verbose_name=_('news type'))
verbose_name=_('News image'), on_delete=models.CASCADE) title = TJSONField(blank=True, null=True, default=None,
news_type = models.ForeignKey( verbose_name=_('title'),
NewsType, verbose_name=_('news type'), on_delete=models.CASCADE) help_text='{"en-GB":"some text"}')
subtitle = TJSONField(blank=True, null=True, default=None,
title = TJSONField( verbose_name=_('subtitle'),
_('title'), null=True, blank=True, help_text='{"en-GB":"some text"}')
default=None, help_text='{"en-GB":"some text"}') description = TJSONField(blank=True, null=True, default=None,
subtitle = TJSONField( verbose_name=_('description'),
_('subtitle'), null=True, blank=True, help_text='{"en-GB":"some text"}')
default=None, help_text='{"en-GB":"some text"}' start = models.DateTimeField(verbose_name=_('Start'))
) end = models.DateTimeField(verbose_name=_('End'))
description = TJSONField(
_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}'
)
start = models.DateTimeField(_('start'))
end = models.DateTimeField(_('end'))
playlist = models.IntegerField(_('playlist')) playlist = models.IntegerField(_('playlist'))
address = models.ForeignKey( is_publish = models.BooleanField(default=False,
'location.Address', verbose_name=_('address'), blank=True, verbose_name=_('Publish status'))
null=True, default=None, on_delete=models.CASCADE) author = models.CharField(max_length=255, blank=True, null=True,
is_publish = models.BooleanField( default=None,verbose_name=_('Author'))
default=False, verbose_name=_('Publish status')) is_highlighted = models.BooleanField(default=False,
country = models.ForeignKey( verbose_name=_('Is highlighted'))
'location.Country', blank=True, null=True,
verbose_name=_('country'), on_delete=models.CASCADE)
is_highlighted = models.BooleanField(
default=False, verbose_name=_('Is highlighted'))
# TODO: metadata_keys - описание ключей для динамического построения полей метаданных # TODO: metadata_keys - описание ключей для динамического построения полей метаданных
# TODO: metadata_values - Описание значений для динамических полей из MetadataKeys # TODO: metadata_values - Описание значений для динамических полей из MetadataKeys
image_url = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Image URL path'))
preview_image_url = models.URLField(blank=True, null=True, default=None,
verbose_name=_('Preview image URL path'))
address = models.ForeignKey('location.Address', blank=True, null=True,
default=None, verbose_name=_('address'),
on_delete=models.SET_NULL)
country = models.ForeignKey('location.Country', blank=True, null=True,
on_delete=models.SET_NULL,
verbose_name=_('country'))
tags = generic.GenericRelation(to='main.MetaDataContent')
objects = NewsQuerySet.as_manager() objects = NewsQuerySet.as_manager()
class Meta: class Meta:
"""Meta class."""
verbose_name = _('news') verbose_name = _('news')
verbose_name_plural = _('news') verbose_name_plural = _('news')

101
apps/news/serializers.py Normal file
View File

@ -0,0 +1,101 @@
"""News app common serializers."""
from rest_framework import serializers
from location import models as location_models
from location.serializers import CountrySimpleSerializer
from main.serializers import MetaDataContentSerializer
from news import models
from utils.serializers import TranslatedField
class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer."""
class Meta:
"""Meta class."""
model = models.NewsType
fields = ('id', 'name')
class NewsBaseSerializer(serializers.ModelSerializer):
"""Base serializer for News model."""
# read only fields
title_translated = TranslatedField()
subtitle_translated = TranslatedField()
# related fields
news_type = NewsTypeSerializer(read_only=True)
tags = MetaDataContentSerializer(read_only=True, many=True)
class Meta:
"""Meta class."""
model = models.News
fields = (
'id',
'title_translated',
'subtitle_translated',
'image_url',
'image_url',
'preview_image_url',
'news_type',
'tags',
)
class NewsDetailSerializer(NewsBaseSerializer):
"""News detail serializer."""
description_translated = TranslatedField()
country = CountrySimpleSerializer(read_only=True)
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
fields = NewsBaseSerializer.Meta.fields + (
'description_translated',
'start',
'end',
'playlist',
'is_highlighted',
'is_publish',
'author',
'country',
)
class NewsBackOfficeBaseSerializer(NewsBaseSerializer):
"""News back office base serializer."""
class Meta(NewsBaseSerializer.Meta):
"""Meta class."""
fields = NewsBaseSerializer.Meta.fields + (
'title',
'subtitle',
)
class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer,
NewsDetailSerializer):
"""News detail serializer for back-office users."""
news_type_id = serializers.PrimaryKeyRelatedField(
source='news_type', write_only=True,
queryset=models.NewsType.objects.all())
country_id = serializers.PrimaryKeyRelatedField(
source='country', write_only=True,
queryset=location_models.Country.objects.all())
class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta):
"""Meta class."""
fields = NewsBackOfficeBaseSerializer.Meta.fields + \
NewsDetailSerializer.Meta.fields + (
'description',
'news_type_id',
'country_id',
)

View File

@ -1,76 +0,0 @@
"""News app common serializers."""
from rest_framework import serializers
from gallery import models as gallery_models
from location.models import Address
from location.serializers import AddressSerializer
from news import models
class NewsTypeSerializer(serializers.ModelSerializer):
"""News type serializer."""
class Meta:
model = models.NewsType
fields = [
'id',
'name'
]
class NewsSerializer(serializers.ModelSerializer):
"""News serializer."""
address = AddressSerializer()
title_translated = serializers.CharField(read_only=True, allow_null=True)
subtitle_translated = serializers.CharField(read_only=True, allow_null=True)
description_translated = serializers.CharField(read_only=True, allow_null=True)
image_url = serializers.ImageField(source='image.image', allow_null=True)
class Meta:
model = models.News
fields = [
'id',
'news_type',
'start',
'end',
'playlist',
'address',
'is_highlighted',
'image_url',
# Localized fields
'title_translated',
'subtitle_translated',
'description_translated',
]
class NewsCreateUpdateSerializer(NewsSerializer):
"""News update serializer."""
title = serializers.JSONField()
subtitle = serializers.JSONField()
description = serializers.JSONField()
image = serializers.PrimaryKeyRelatedField(
queryset=gallery_models.Image.objects.all(), required=True,)
news_type = serializers.PrimaryKeyRelatedField(
queryset=models.NewsType.objects.all(), write_only=True)
address = serializers.PrimaryKeyRelatedField(
queryset=Address.objects.all(), write_only=True)
class Meta:
model = models.News
read_only_fields = [
'id'
]
fields = [
'id',
'news_type',
'title',
'subtitle',
'description',
'start',
'end',
'playlist',
'address',
'image',
'is_publish',
'country'
]

View File

@ -1,3 +1,44 @@
from http.cookies import SimpleCookie
from django.test import TestCase from django.test import TestCase
from rest_framework.test import APITestCase
from rest_framework import status
from news.models import NewsType, News
from account.models import User
# Create your tests here. # Create your tests here.
class BaseTestCase(APITestCase):
def setUp(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
#get tokkens
tokkens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token')})
self.test_news_type = NewsType.objects.create(name="Test news type")
self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"},
news_type=self.test_news_type, description={"en-GB": "Description test news"},
playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00",
is_publish=True)
class NewsTestCase(BaseTestCase):
def test_news_list(self):
response = self.client.get("/api/web/news/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_news_detail(self):
response = self.client.get(f"/api/web/news/{self.test_news.id}/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_news_type_list(self):
response = self.client.get("/api/web/news/type/")
self.assertEqual(response.status_code, status.HTTP_200_OK)

11
apps/news/urls/back.py Normal file
View File

@ -0,0 +1,11 @@
"""News app urlpatterns for backoffice"""
from django.urls import path
from news import views
app_name = 'news'
urlpatterns = [
path('', views.NewsBackOfficeLCView.as_view(), name='list-create'),
path('<int:pk>/', views.NewsBackOfficeRUDView.as_view(),
name='retrieve-update-destroy'),
]

View File

@ -1,12 +1,11 @@
"""Location app urlconf.""" """News app urlconf."""
from django.urls import path from django.urls import path
from news import views
from news.views import common
app_name = 'news' app_name = 'news'
urlpatterns = [ urlpatterns = [
path('', common.NewsListView.as_view(), name='list'), path('', views.NewsListView.as_view(), name='list'),
path('<int:pk>/', common.NewsDetailView.as_view(), name='rud'), path('<int:pk>/', views.NewsDetailView.as_view(), name='rud'),
path('type/', common.NewsTypeListView.as_view(), name='type'), path('types/', views.NewsTypeListView.as_view(), name='type'),
] ]

68
apps/news/views.py Normal file
View File

@ -0,0 +1,68 @@
"""News app views."""
from rest_framework import generics, permissions
from news import filters, models, serializers
class NewsMixinView:
"""News mixin."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.NewsBaseSerializer
def get_queryset(self, *args, **kwargs):
"""Override get_queryset method."""
qs = models.News.objects.with_related().published()\
.order_by('-is_highlighted', '-created')
if self.request.country_code:
qs = qs.by_country_code(self.request.country_code)
return qs
class NewsListView(NewsMixinView, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet
class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""News detail view."""
serializer_class = serializers.NewsDetailSerializer
class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
pagination_class = None
permission_classes = (permissions.AllowAny, )
queryset = models.NewsType.objects.all()
serializer_class = serializers.NewsTypeSerializer
class NewsBackOfficeMixinView:
"""News back office mixin view."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.News.objects.with_related() \
.order_by('-is_highlighted', '-created')
class NewsBackOfficeLCView(NewsBackOfficeMixinView,
generics.ListCreateAPIView):
"""Resource for a list of news for back-office users."""
serializer_class = serializers.NewsBackOfficeBaseSerializer
create_serializers_class = serializers.NewsBackOfficeDetailSerializer
def get_serializer_class(self):
if self.request.method == 'POST':
return self.create_serializers_class
return super().get_serializer_class()
class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
generics.RetrieveUpdateDestroyAPIView):
"""Resource for detailed information about news for back-office users."""
serializer_class = serializers.NewsBackOfficeDetailSerializer

View File

@ -1,38 +0,0 @@
"""News app common app."""
from rest_framework import generics, permissions
from news import filters, models
from news.serializers import common as serializers
from utils.views import JWTGenericViewMixin
class NewsMixin:
"""News mixin."""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.NewsSerializer
def get_queryset(self, *args, **kwargs):
"""Override get_queryset method"""
return models.News.objects.published() \
.by_country_code(code=self.request.country_code) \
.order_by('-is_highlighted', '-created')
class NewsListView(NewsMixin, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet
class NewsDetailView(NewsMixin, JWTGenericViewMixin, generics.RetrieveAPIView):
"""News detail view."""
class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
serializer_class = serializers.NewsTypeSerializer
permission_classes = (permissions.AllowAny, )
pagination_class = None
queryset = models.NewsType.objects.all()

View File

View File

@ -1 +1,16 @@
# Create your tests here. # Create your tests here.
from rest_framework.test import APITestCase
from rest_framework import status
from partner.models import Partner
class PartnerTestCase(APITestCase):
def setUp(self):
self.test_url = "www.example.com"
self.test_partner = Partner.objects.create(url=self.test_url)
def test_partner_list(self):
response = self.client.get("/api/web/partner/")
self.assertEqual(response.status_code, status.HTTP_200_OK)

33
apps/recipe/tests.py Normal file
View File

@ -0,0 +1,33 @@
from http.cookies import SimpleCookie
from rest_framework.test import APITestCase
from rest_framework import status
from account.models import User
from recipe.models import Recipe
class BaseTestCase(APITestCase):
def setUp(self):
self.username = 'sedragurda'
self.password = 'sedragurdaredips19'
self.email = 'sedragurda@desoz.com'
self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password)
tokkens = User.create_jwt_tokens(self.user)
self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'),
'refresh_token': tokkens.get('refresh_token')})
self.test_recipe = Recipe.objects.create(title={"en-GB": "test title"}, description={"en-GB": "test description"},
state=2, author="Test Author", created_by=self.user,
modified_by=self.user)
def test_recipe_list(self):
response = self.client.get("/api/web/recipes/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_recipe_detail(self):
print(self.test_recipe.id)
response = self.client.get(f"/api/web/recipes/{self.test_recipe.id}/")
print(response.json())
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -4,7 +4,7 @@ from search_indexes import views
router = routers.SimpleRouter() router = routers.SimpleRouter()
router.register(r'news', views.NewsDocumentViewSet, basename='news') # router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled
router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment')
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -1,6 +1,5 @@
"""Utils app serializer.""" """Utils app serializer."""
from rest_framework import serializers from rest_framework import serializers
from utils.models import PlatformMixin from utils.models import PlatformMixin
@ -13,3 +12,12 @@ class SourceSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=PlatformMixin.SOURCES, source = serializers.ChoiceField(choices=PlatformMixin.SOURCES,
default=PlatformMixin.WEB, default=PlatformMixin.WEB,
write_only=True) write_only=True)
class TranslatedField(serializers.CharField):
"""Translated field."""
def __init__(self, allow_null=True, required=False, read_only=True,
**kwargs):
super().__init__(allow_null=allow_null, required=required,
read_only=read_only, **kwargs)

View File

@ -24,7 +24,7 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'development_news', # 'search_indexes.documents.news': 'development_news', # temporarily disabled
'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.establishment': 'development_establishment',
} }

View File

@ -64,6 +64,6 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'local_news', # 'search_indexes.documents.news': 'local_news',
'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.establishment': 'local_establishment',
} }

View File

@ -22,6 +22,6 @@ ELASTICSEARCH_DSL = {
ELASTICSEARCH_INDEX_NAMES = { ELASTICSEARCH_INDEX_NAMES = {
'search_indexes.documents.news': 'stage_news', # 'search_indexes.documents.news': 'stage_news', #temporarily disabled
'search_indexes.documents.establishment': 'stage_establishment', 'search_indexes.documents.establishment': 'stage_establishment',
} }

View File

@ -7,4 +7,5 @@ urlpatterns = [
namespace='gallery')), namespace='gallery')),
path('establishments/', include('establishment.urls.back')), path('establishments/', include('establishment.urls.back')),
path('location/', include('location.urls.back')), path('location/', include('location.urls.back')),
path('news/', include('news.urls.back'))
] ]