Merge branch 'develop' into feature/gm-148

# Conflicts:
#	apps/news/models.py
This commit is contained in:
Anatoly 2019-10-04 11:53:31 +03:00
commit 646eeb8972
18 changed files with 193 additions and 8 deletions

View File

@ -4,6 +4,20 @@ from django.db import migrations, models
import uuid
def fill_uuid(apps, schemaeditor):
Advertisement = apps.get_model('advertisement', 'Advertisement')
for a in Advertisement.objects.all():
a.uuid = uuid.uuid4()
a.save()
def fill_block_level(apps, schemaeditor):
Advertisement = apps.get_model('advertisement', 'Advertisement')
for a in Advertisement.objects.all():
a.block_level = ''
a.save()
class Migration(migrations.Migration):
dependencies = [
@ -23,6 +37,12 @@ class Migration(migrations.Migration):
field=models.ManyToManyField(to='translation.Language'),
),
migrations.AddField(
model_name='advertisement',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, editable=False),
),
migrations.RunPython(fill_uuid, migrations.RunPython.noop),
migrations.AlterField(
model_name='advertisement',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
@ -32,8 +52,14 @@ class Migration(migrations.Migration):
name='block_level',
),
migrations.AddField(
model_name='advertisement',
name='block_level',
field=models.CharField(blank=True, null=True, max_length=10, verbose_name='Block level')
),
migrations.RunPython(fill_block_level, migrations.RunPython.noop),
migrations.AlterField(
model_name='advertisement',
name='block_level',
field=models.CharField(max_length=10, verbose_name='Block level')
)
),
]

View File

@ -4,7 +4,7 @@ from django.contrib.auth import authenticate
from django.contrib.auth import password_validation as password_validators
from django.db.models import Q
from rest_framework import serializers
from rest_framework import validators as rest_validators
from rest_framework.generics import get_object_or_404
from account import models as account_models
from authorization import tasks
@ -76,6 +76,39 @@ class SignupSerializer(serializers.ModelSerializer):
return obj
class ReconfirmSerializer(serializers.ModelSerializer):
class Meta:
model = account_models.User
fields = ('email',)
def validate_email(self, value):
"""Validate email"""
users = list(account_models.User.objects.filter(email=value.lower()).all())
if not users:
raise serializers.ValidationError(detail='User with mentioned email does not exist.')
users = list(filter(lambda user: not user.email_confirmed, users))
if not users:
raise serializers.ValidationError(detail='User with this email is confirmed.')
return value
def create(self, validated_data):
"""Override create method"""
queryset = account_models.User.objects.all()
email = validated_data.get('email').lower()
country_code = self.context.get('request').country_code
obj = get_object_or_404(queryset, email=email)
if settings.USE_CELERY:
tasks.send_confirm_email.delay(
user_id=obj.id,
country_code=country_code)
else:
tasks.send_confirm_email(
user_id=obj.id,
country_code=country_code)
return obj
class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
serializers.ModelSerializer):
"""Serializer for login user"""

View File

@ -29,6 +29,7 @@ urlpatterns_oauth2 = [
urlpatterns_jwt = [
path('signup/', views.SignUpView.as_view(), name='signup'),
path('signup/reconfirm/', views.ReconfirmView.as_view(), name='signup-reconfirm'),
path('signup/confirm/<uidb64>/<token>/', views.ConfirmationEmailView.as_view(),
name='signup-confirm'),
path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'),

View File

@ -147,6 +147,17 @@ class SignUpView(generics.GenericAPIView):
return Response(status=status.HTTP_201_CREATED)
class ReconfirmView(generics.CreateAPIView):
""" Resends confirmation email whether user's still not confirmed """
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.ReconfirmSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(status=status.HTTP_201_CREATED)
class ConfirmationEmailView(JWTGenericViewMixin):
"""View for confirmation email"""

View File

@ -349,7 +349,9 @@ class Carousel(models.Model):
@property
def model_name(self):
return self.content_object.__class__.__name__
if hasattr(self.content_object, 'establishment_type'):
return self.content_object.establishment_type.name_translated
class Page(models.Model):

View File

@ -1,11 +1,13 @@
"""News app models."""
from django.contrib.contenttypes import fields as generic
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 rest_framework.reverse import reverse
from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin
from django.contrib.contenttypes.models import ContentType
from rating.models import Rating
from random import sample as random_sample
class NewsType(models.Model):
@ -27,6 +29,9 @@ class NewsType(models.Model):
class NewsQuerySet(models.QuerySet):
"""QuerySet for model News"""
def rating_value(self):
return self.annotate(rating=models.Count('ratings__ip', distinct=True))
def with_base_related(self):
"""Return qs with related objects."""
return self.select_related('news_type', 'country').prefetch_related('tags')
@ -129,6 +134,7 @@ class News(BaseAttributes, TranslatedFieldsMixin):
tags = generic.GenericRelation(to='main.MetaDataContent')
gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
ratings = generic.GenericRelation(Rating)
objects = NewsQuerySet.as_manager()
@ -149,6 +155,14 @@ class News(BaseAttributes, TranslatedFieldsMixin):
def web_url(self):
return reverse('web:news:rud', kwargs={'slug': self.slug})
@property
def should_read(self):
return self.__class__.objects.should_read(self)[:3]
@property
def same_theme(self):
return self.__class__.objects.same_theme(self)[:3]
class NewsGalleryQuerySet(models.QuerySet):
"""QuerySet for model News"""

View File

@ -7,7 +7,7 @@ from rest_framework.response import Response
from gallery.tasks import delete_image
from news import filters, models, serializers
from rating.tasks import add_rating
class NewsMixinView:
"""News mixin."""
@ -40,7 +40,6 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView):
"""Override get_queryset method."""
return super().get_queryset().with_extended_related()
class NewsTypeListView(generics.ListAPIView):
"""NewsType list view."""
@ -140,3 +139,7 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView,
serializer_class = serializers.NewsBackOfficeDetailSerializer
def get(self, request, pk, *args, **kwargs):
add_rating(remote_addr=request.META.get('REMOTE_ADDR'),
pk=pk, model='news', app_label='news')
return self.retrieve(request, *args, **kwargs)

0
apps/rating/__init__.py Normal file
View File

11
apps/rating/admin.py Normal file
View File

@ -0,0 +1,11 @@
from django.contrib import admin
from rating import models
from rating import tasks
@admin.register(models.Rating)
class RatingAdmin(admin.ModelAdmin):
"""Rating type admin conf."""
list_display = ['name', 'ip']
list_display_links = ['name']

5
apps/rating/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class RatingConfig(AppConfig):
name = 'rating'

View File

@ -0,0 +1,28 @@
# Generated by Django 2.2.4 on 2019-10-02 11:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Rating',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('ip', models.GenericIPAddressField(verbose_name='ip')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
options={
'verbose_name': 'rating',
},
),
]

View File

19
apps/rating/models.py Normal file
View File

@ -0,0 +1,19 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import gettext_lazy as _
class Rating(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
ip = models.GenericIPAddressField(verbose_name=_('ip'))
@property
def name(self):
# Check if Generic obj has name or title
if hasattr(self.content_object, 'name'):
return self.content_object.name
if hasattr(self.content_object, 'title'):
return self.content_object.title_translated

22
apps/rating/tasks.py Normal file
View File

@ -0,0 +1,22 @@
from datetime import timedelta
from celery import task
from rating.models import Rating
from django.contrib.contenttypes.models import ContentType
def add_rating(remote_addr, pk, model, app_label):
add.apply_async(
(remote_addr, pk, model, app_label), countdown=60 * 60
)
@task
def add(remote_addr, pk, model, app_label):
rating = Rating()
rating.ip = remote_addr
rating.object_id = pk
rating.content_type = ContentType.objects.get(app_label=app_label, model=model)
rating.save()

3
apps/rating/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
apps/rating/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -60,7 +60,10 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet):
'description',
)
filter_fields = {
'tag': 'tags.id',
'tag': {
'field': 'tags.id',
'lookups': [constants.LOOKUP_QUERY_IN]
},
'toque_number': {
'field': 'toque_number',
'lookups': [

View File

@ -71,6 +71,7 @@ PROJECT_APPS = [
'review.apps.ReviewConfig',
'comment.apps.CommentConfig',
'favorites.apps.FavoritesConfig',
'rating.apps.RatingConfig',
]
EXTERNAL_APPS = [