From e565406729509fa9f3028c63ece228cdfbe0b985 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Fri, 22 Nov 2019 14:21:25 +0000 Subject: [PATCH] Feature/bo employee update --- apps/establishment/filters.py | 20 +++++++ .../migrations/0066_auto_20191122_1144.py | 28 +++++++++ .../migrations/0067_auto_20191122_1244.py | 39 ++++++++++++ apps/establishment/models.py | 59 +++++++++++++++++-- apps/establishment/serializers/back.py | 46 ++++++++++++++- apps/establishment/serializers/common.py | 57 +++++++++++++++++- apps/establishment/urls/back.py | 8 +++ apps/establishment/views/back.py | 44 +++++++++++++- 8 files changed, 289 insertions(+), 12 deletions(-) create mode 100644 apps/establishment/migrations/0066_auto_20191122_1144.py create mode 100644 apps/establishment/migrations/0067_auto_20191122_1244.py diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index adbcae76..db419989 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -55,3 +55,23 @@ class EstablishmentTypeTagFilter(filters.FilterSet): fields = ( 'type_id', ) + + +class EmployeeBackFilter(filters.FilterSet): + """Employee filter set.""" + + search = filters.CharFilter(method='search_by_name_or_last_name') + + class Meta: + """Meta class.""" + + model = models.Employee + fields = ( + 'search', + ) + + def search_by_name_or_last_name(self, queryset, name, value): + """Search by name or last name.""" + if value not in EMPTY_VALUES: + return queryset.search_by_name_or_last_name(value) + return queryset diff --git a/apps/establishment/migrations/0066_auto_20191122_1144.py b/apps/establishment/migrations/0066_auto_20191122_1144.py new file mode 100644 index 00000000..edff3333 --- /dev/null +++ b/apps/establishment/migrations/0066_auto_20191122_1144.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.7 on 2019-11-22 11:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0065_establishment_purchased_products'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='last_name', + field=models.CharField(default=None, max_length=255, null=True, verbose_name='Last Name'), + ), + migrations.AddField( + model_name='establishmentemployee', + name='status', + field=models.CharField(choices=[('I', 'Idle'), ('A', 'Accepted'), ('D', 'Declined')], default='I', max_length=1), + ), + migrations.AlterField( + model_name='employee', + name='name', + field=models.CharField(max_length=255, verbose_name='Name'), + ), + ] diff --git a/apps/establishment/migrations/0067_auto_20191122_1244.py b/apps/establishment/migrations/0067_auto_20191122_1244.py new file mode 100644 index 00000000..8dfdc3a4 --- /dev/null +++ b/apps/establishment/migrations/0067_auto_20191122_1244.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.7 on 2019-11-22 12:44 + +from django.db import migrations, models +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0066_auto_20191122_1144'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='birth_date', + field=models.DateTimeField(default=None, null=True, verbose_name='Birth date'), + ), + migrations.AddField( + model_name='employee', + name='email', + field=models.EmailField(blank=True, default=None, max_length=254, null=True, verbose_name='Email'), + ), + migrations.AddField( + model_name='employee', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(default=None, max_length=128, null=True), + ), + migrations.AddField( + model_name='employee', + name='sex', + field=models.PositiveSmallIntegerField(choices=[(0, 'Male'), (1, 'Female')], default=None, null=True, verbose_name='Sex'), + ), + migrations.AddField( + model_name='employee', + name='toque_number', + field=models.PositiveSmallIntegerField(default=None, null=True, verbose_name='Toque number'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index f8ffd456..d22aebaa 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,6 +1,7 @@ """Establishment models.""" from datetime import datetime from functools import reduce +from typing import List from operator import or_ import elasticsearch_dsl @@ -435,11 +436,12 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, @property def visible_tags(self): - return super().visible_tags\ - .exclude(category__index_name__in=['guide', 'collection', 'purchased_item', - 'business_tag', 'business_tags_de'])\ + return super().visible_tags \ + .exclude(category__index_name__in=['guide', 'collection', 'purchased_item', + 'business_tag', 'business_tags_de']) \ + \ + # todo: recalculate toque_number - # todo: recalculate toque_number def recalculate_toque_number(self): toque_number = 0 if self.address and self.public_mark: @@ -612,7 +614,6 @@ class EstablishmentNote(ProjectBaseMixin): class EstablishmentGallery(IntermediateGalleryModelMixin): - establishment = models.ForeignKey(Establishment, null=True, related_name='establishment_gallery', on_delete=models.CASCADE, @@ -663,6 +664,16 @@ class EstablishmentEmployeeQuerySet(models.QuerySet): class EstablishmentEmployee(BaseAttributes): """EstablishmentEmployee model.""" + IDLE = 'I' + ACCEPTED = 'A' + DECLINED = 'D' + + STATUS_CHOICES = ( + (IDLE, 'Idle'), + (ACCEPTED, 'Accepted'), + (DECLINED, 'Declined'), + ) + establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, verbose_name=_('Establishment')) employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT, @@ -673,19 +684,53 @@ class EstablishmentEmployee(BaseAttributes): verbose_name=_('To date')) position = models.ForeignKey(Position, on_delete=models.PROTECT, verbose_name=_('Position')) + + status = models.CharField(max_length=1, choices=STATUS_CHOICES, default=IDLE) + # old_id = affiliations_id old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True) objects = EstablishmentEmployeeQuerySet.as_manager() +class EmployeeQuerySet(models.QuerySet): + + def _generic_search(self, value, filter_fields_names: List[str]): + """Generic method for searching value in specified fields""" + filters = [ + {f'{field}__icontains': value} + for field in filter_fields_names + ] + return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters])) + + def search_by_name_or_last_name(self, value): + """Search by name or last_name.""" + return self._generic_search(value, ['name', 'last_name']) + + 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')) + name = models.CharField(max_length=255, verbose_name=_('Name')) + last_name = models.CharField(max_length=255, verbose_name=_('Last Name'), null=True, default=None) + + # SEX CHOICES + MALE = 0 + FEMALE = 1 + + SEX_CHOICES = ( + (MALE, _('Male')), + (FEMALE, _('Female')) + ) + sex = models.PositiveSmallIntegerField(choices=SEX_CHOICES, verbose_name=_('Sex'), null=True, default=None) + birth_date = models.DateTimeField(editable=True, verbose_name=_('Birth date'), null=True, default=None) + email = models.EmailField(blank=True, null=True, default=None, verbose_name=_('Email')) + phone = PhoneNumberField(null=True, default=None) + toque_number = models.PositiveSmallIntegerField(verbose_name=_('Toque number'), null=True, default=None) + establishments = models.ManyToManyField(Establishment, related_name='employees', through=EstablishmentEmployee, ) awards = generic.GenericRelation(to='main.Award', related_query_name='employees') @@ -694,6 +739,8 @@ class Employee(BaseAttributes): # old_id = profile_id old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True) + objects = EmployeeQuerySet.as_manager() + class Meta: """Meta class.""" diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index a78bce07..52af0574 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -2,8 +2,9 @@ from rest_framework import serializers from establishment import models from establishment import serializers as model_serializers -from location.serializers import AddressDetailSerializer +from location.serializers import AddressDetailSerializer, TranslatedField from main.models import Currency +from main.serializers import AwardSerializer from utils.decorators import with_base_attributes from utils.serializers import TimeZoneChoiceField from gallery.models import Image @@ -161,12 +162,53 @@ class ContactEmailBackSerializers(model_serializers.PlateSerializer): class EmployeeBackSerializers(serializers.ModelSerializer): """Employee serializers.""" + awards = AwardSerializer(many=True) + class Meta: model = models.Employee fields = [ 'id', 'user', - 'name' + 'name', + 'last_name', + 'sex', + 'birth_date', + 'email', + 'phone', + 'toque_number', + 'awards', + ] + + +class PositionBackSerializer(serializers.ModelSerializer): + """Position Back serializer.""" + + name_translated = TranslatedField() + + class Meta: + model = models.Position + fields = [ + 'id', + 'name_translated', + 'priority', + 'index_name', + ] + + +class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer): + """Establishment Employee serializer.""" + + employee = EmployeeBackSerializers() + position = PositionBackSerializer() + + class Meta: + model = models.EstablishmentEmployee + fields = [ + 'id', + 'employee', + 'from_date', + 'to_date', + 'position', ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index c85b3123..9419c449 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -168,12 +168,51 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): awards = AwardSerializer(source='employee.awards', many=True) priority = serializers.IntegerField(source='position.priority') position_index_name = serializers.CharField(source='position.index_name') + status = serializers.CharField() class Meta: """Meta class.""" model = models.Employee - fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name') + fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name', 'status') + + +class EstablishmentEmployeeCreateSerializer(serializers.ModelSerializer): + """Serializer for establishment employee relation.""" + + class Meta: + """Meta class.""" + + model = models.EstablishmentEmployee + fields = ('id',) + + def _validate_entity(self, entity_id_param: str, entity_class): + entity_id = self.context.get('request').parser_context.get('kwargs').get(entity_id_param) + entity_qs = entity_class.objects.filter(id=entity_id) + if not entity_qs.exists(): + raise serializers.ValidationError({'detail': _(f'{entity_class.__name__} not found.')}) + return entity_qs.first() + + def validate(self, attrs): + """Override validate method""" + establishment = self._validate_entity("establishment_id", models.Establishment) + employee = self._validate_entity("employee_id", models.Employee) + position = self._validate_entity("position_id", models.Position) + + attrs['establishment'] = establishment + attrs['employee'] = employee + attrs['position'] = position + + return attrs + + def create(self, validated_data, *args, **kwargs): + """Override create method""" + validated_data.update({ + 'employee': validated_data.pop('employee'), + 'establishment': validated_data.pop('establishment'), + 'position': validated_data.pop("position") + }) + return super().create(validated_data) class EstablishmentShortSerializer(serializers.ModelSerializer): @@ -396,6 +435,22 @@ class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer return super().create(validated_data) +class EstablishmentCommentRUDSerializer(comment_serializers.CommentSerializer): + """Retrieve/Update/Destroy comment serializer.""" + + class Meta: + """Meta class.""" + model = comment_models.Comment + fields = [ + 'id', + 'created', + 'text', + 'mark', + 'nickname', + 'profile_pic', + ] + + class EstablishmentFavoritesCreateSerializer(FavoritesCreateSerializer): """Serializer to favorite object w/ model Establishment.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index f06e2187..a06adc3b 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -38,8 +38,16 @@ urlpatterns = [ path('phones//', views.PhonesRUDView.as_view(), name='phones-rud'), path('emails/', views.EmailListCreateView.as_view(), name='emails'), path('emails//', views.EmailRUDView.as_view(), name='emails-rud'), + path('/employees/', views.EstablishmentEmployeeListView.as_view(), + name='establishment-employees'), path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), + path('/employee//position/', + views.EstablishmentEmployeeCreateView.as_view(), + name='employees-establishment-create'), + path('/employee/', + views.EstablishmentEmployeeDeleteView.as_view(), + name='employees-establishment-delete'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index d1897397..312bb171 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,7 +1,9 @@ """Establishment app views.""" +from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions +from rest_framework import generics, permissions, status +from utils.permissions import IsCountryAdmin, IsEstablishmentManager from establishment import filters, models, serializers from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.permissions import IsCountryAdmin, IsEstablishmentManager @@ -43,8 +45,8 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """ Returns the object the view is displaying. """ - establishment_pk = self.kwargs.get('pk') - schedule_id = self.kwargs.get('schedule_id') + establishment_pk = self.kwargs['pk'] + schedule_id = self.kwargs['schedule_id'] establishment = get_object_or_404(klass=models.Establishment.objects.all(), pk=establishment_pk) @@ -156,11 +158,23 @@ class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): class EmployeeListCreateView(generics.ListCreateAPIView): """Emplyoee list create view.""" + permission_classes = (permissions.AllowAny, ) + filter_class = filters.EmployeeBackFilter serializer_class = serializers.EmployeeBackSerializers queryset = models.Employee.objects.all() pagination_class = None +class EstablishmentEmployeeListView(generics.ListAPIView): + """Establishment emplyoees list view.""" + permission_classes = (permissions.AllowAny, ) + serializer_class = serializers.EstablishmentEmployeeBackSerializer + + def get_queryset(self): + establishment_id = self.kwargs['establishment_id'] + return models.EstablishmentEmployee.objects.filter(establishment__id=establishment_id) + + class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): """Employee RUD view.""" serializer_class = serializers.EmployeeBackSerializers @@ -318,3 +332,27 @@ class EstablishmentNoteRUDView(EstablishmentMixinViews, self.check_object_permissions(self.request, note) return note + + +class EstablishmentEmployeeCreateView(generics.CreateAPIView): + serializer_class = serializers.EstablishmentEmployeeCreateSerializer + queryset = models.EstablishmentEmployee.objects.all() + # TODO send email to all admins and add endpoint for changing status + + +class EstablishmentEmployeeDeleteView(generics.DestroyAPIView): + + def _get_object_to_delete(self, establishment_id, employee_id): + result_qs = models.EstablishmentEmployee\ + .objects\ + .filter(establishment_id=establishment_id, employee_id=employee_id) + if not result_qs.exists(): + raise Http404 + return result_qs.first() + + def delete(self, request, *args, **kwargs): + establishment_id = self.kwargs["establishment_id"] + employee_id = self.kwargs["employee_id"] + object_to_delete = self._get_object_to_delete(establishment_id, employee_id) + object_to_delete.delete() + return HttpResponse(status=status.HTTP_204_NO_CONTENT)