diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index bb2d7ee3..93c669ef 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -1,7 +1,6 @@ """Establishment admin conf.""" from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline - from establishment import models from main.models import Award, MetaDataContent from review import models as review_models @@ -38,6 +37,7 @@ class ContactEmailInline(admin.TabularInline): model = models.ContactEmail extra = 0 + class ReviewInline(GenericTabularInline): model = review_models.Review extra = 0 @@ -60,3 +60,8 @@ class EstablishmentSchedule(admin.ModelAdmin): @admin.register(models.Comment) class EstablishmentComment(admin.ModelAdmin): """Establishment comments.""" + + +@admin.register(models.Position) +class PositionAdmin(admin.ModelAdmin): + """Position admin.""" diff --git a/apps/establishment/migrations/0010_auto_20190901_1142.py b/apps/establishment/migrations/0010_auto_20190901_1142.py new file mode 100644 index 00000000..9a76424b --- /dev/null +++ b/apps/establishment/migrations/0010_auto_20190901_1142.py @@ -0,0 +1,81 @@ +# Generated by Django 2.2.4 on 2019-09-01 11:42 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('establishment', '0009_merge_20190901_1131'), + ] + + operations = [ + migrations.CreateModel( + name='Employee', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('name', models.CharField(max_length=255, verbose_name='Last name')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')), + ], + options={ + 'verbose_name': 'Employee', + 'verbose_name_plural': 'Employees', + }, + ), + migrations.CreateModel( + name='Position', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('name', utils.models.TJSONField(blank=True, default=None, help_text='{"en":"some text"}', null=True, verbose_name='Description')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='position_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')), + ('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='position_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')), + ], + options={ + 'verbose_name': 'Position', + 'verbose_name_plural': 'Positions', + }, + bases=(models.Model, utils.models.TraslatedFieldsMixin), + ), + migrations.CreateModel( + name='EstablishmentEmployee', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('from_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='From date')), + ('to_date', models.DateTimeField(blank=True, default=None, null=True, verbose_name='To date')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentemployee_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Employee', verbose_name='Employee')), + ('establishment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Establishment', verbose_name='Establishment')), + ('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentemployee_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')), + ('position', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='establishment.Position', verbose_name='Position')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='employee', + name='establishments', + field=models.ManyToManyField(related_name='employees', through='establishment.EstablishmentEmployee', to='establishment.Establishment'), + ), + migrations.AddField( + model_name='employee', + name='modified_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by'), + ), + migrations.AddField( + model_name='employee', + name='user', + field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index a2cbb11e..e31fdc23 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,15 +1,14 @@ """Establishment models.""" +from functools import reduce from django.contrib.contenttypes import fields as generic from django.core.exceptions import ValidationError from django.db import models +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField - from location.models import Address -from utils.models import ( - ProjectBaseMixin, ImageMixin, TJSONField, - TraslatedFieldsMixin, BaseAttributes -) +from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, + TraslatedFieldsMixin, BaseAttributes) # todo: establishment type&subtypes check @@ -73,6 +72,14 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() + def prefetch_actual_employees(self): + """Prefetch actual employees.""" + return self.prefetch_related( + models.Prefetch('establishmentemployee_set', + queryset=EstablishmentEmployee.objects.actual().select_related( + 'position'), + to_attr='actual_establishment_employees')) + class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin): """Establishment model.""" @@ -127,6 +134,7 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin): country = self.address.city.country return country.low_price, country.high_price + # todo: make via prefetch @property def subtypes(self): return EstablishmentSubType.objects.filter( @@ -145,6 +153,65 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin): self.establishment_subtypes.add(establishment_subtype) +class Position(BaseAttributes, TraslatedFieldsMixin): + """Position model.""" + + name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), + help_text='{"en":"some text"}') + + class Meta: + """Meta class.""" + + verbose_name = _('Position') + verbose_name_plural = _('Positions') + + +class EstablishmentEmployeeQuerySet(models.QuerySet): + """Extended queryset for EstablishmEntemployee model.""" + + def actual(self): + """Actual objects..""" + now = timezone.now() + return self.filter(models.Q(from_date__lte=now), + (models.Q(to_date__gte=now) | + models.Q(to_date__isnull=True))) + + +class EstablishmentEmployee(BaseAttributes): + """EstablishmentEmployee model.""" + + establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, + verbose_name=_('Establishment')) + employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT, + verbose_name=_('Employee')) + from_date = models.DateTimeField(default=timezone.now, verbose_name=_('From date')) + to_date = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('To date')) + position = models.ForeignKey(Position, on_delete=models.PROTECT, + verbose_name=_('Position')) + + objects = EstablishmentEmployeeQuerySet.as_manager() + + +class Employee(BaseAttributes): + """Employee model.""" + + user = models.OneToOneField('account.User', on_delete=models.PROTECT, + null=True, blank=True, default=None, + verbose_name=_('User')) + name = models.CharField(max_length=255, verbose_name=_('Last name')) + establishments = models.ManyToManyField(Establishment, related_name='employees', + through=EstablishmentEmployee) + awards = generic.GenericRelation(to='main.Award') + tags = generic.GenericRelation(to='main.MetaDataContent') + + class Meta: + """Meta class.""" + + verbose_name = _('Employee') + verbose_name_plural = _('Employees') + + class EstablishmentScheduleQuerySet(models.QuerySet): """QuerySet for model EstablishmentSchedule""" diff --git a/apps/establishment/serializers.py b/apps/establishment/serializers.py index cf26306d..ac508c03 100644 --- a/apps/establishment/serializers.py +++ b/apps/establishment/serializers.py @@ -1,6 +1,5 @@ """Establishment serializers.""" from rest_framework import serializers - from establishment import models from location.serializers import AddressSerializer from main.serializers import MetaDataContentSerializer, AwardSerializer @@ -93,6 +92,19 @@ class CommentSerializer(serializers.ModelSerializer): 'image' ) +class EstablishmentEmployeeSerializer(serializers.ModelSerializer): + """Serializer for actual employees.""" + + id = serializers.IntegerField(source='employee.id') + name = serializers.CharField(source='employee.name') + position_translated = serializers.CharField(source='position.name_translated') + + class Meta: + """Meta class.""" + + model = models.Employee + fields = ('id', 'name', 'position_translated') + class EstablishmentSerializer(serializers.ModelSerializer): """Serializer for Establishment model.""" @@ -109,10 +121,10 @@ class EstablishmentSerializer(serializers.ModelSerializer): allow_null=True) phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) - reviews = ReviewSerializer(source='reviews.last', - allow_null=True) - comments = CommentSerializer(many=True, - allow_null=True) + reviews = ReviewSerializer(source='reviews.last', allow_null=True) + comments = CommentSerializer(many=True, allow_null=True) + employees = EstablishmentEmployeeSerializer(source='actual_establishment_employees', + many=True) class Meta: """Meta class.""" @@ -137,4 +149,5 @@ class EstablishmentSerializer(serializers.ModelSerializer): 'emails', 'reviews', 'comments', + 'employees', ) diff --git a/apps/establishment/views.py b/apps/establishment/views.py index 459aeeac..10f47b01 100644 --- a/apps/establishment/views.py +++ b/apps/establishment/views.py @@ -5,22 +5,26 @@ from utils.views import JWTGenericViewMixin from establishment import filters -class EstablishmentListView(JWTGenericViewMixin, generics.ListAPIView): - """Resource for getting a list of establishments.""" +class EstablishmentMixin: + """Establishment mixin.""" permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentSerializer - queryset = models.Establishment.objects.all() + + def get_queryset(self): + """Overrided method 'get_queryset'.""" + return models.Establishment.objects.all().prefetch_actual_employees() + + +class EstablishmentListView(EstablishmentMixin, JWTGenericViewMixin, generics.ListAPIView): + """Resource for getting a list of establishments.""" + filter_class = filters.EstablishmentFilter -class EstablishmentRetrieveView(JWTGenericViewMixin, generics.RetrieveAPIView): +class EstablishmentRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.RetrieveAPIView): """Resource for getting a establishment.""" - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.EstablishmentSerializer - queryset = models.Establishment.objects.all() - class EstablishmentTypeListView(JWTGenericViewMixin, generics.ListAPIView): """Resource for getting a list of establishment types."""