diff --git a/app/conf/api.py b/app/conf/api.py index 89b75c4..6422def 100644 --- a/app/conf/api.py +++ b/app/conf/api.py @@ -1,18 +1,26 @@ from django.urls import path, include from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView -from events.api.views import ListCreateEventApi, RetireUpdateDeleteEventApi -from marketplace.api.views import ListCreateProductApi, RetireUpdateDestroyProductApi +from events.api.views import ( + ListCreateEventApi, + RetrieveUpdateDeleteEventApi, + ListPlannedEvents, + RetrieveSubmitDeleteEventAttendance, + ListAttendedWorkersApi, + SubmitWorkerAttendedEvent, +) +from marketplace.api.views import ListCreateProductApi, RetrieveUpdateDestroyProductApi from users.api.views import ( ListCreateUserApi, - RetireUpdateDeleteUserApi, + RetrieveUpdateDeleteUserApi, ListCreateDepartmentApi, - RetireUpdateDeleteDepartmentApi, + RetrieveUpdateDeleteDepartmentApi, ListCreateStreamApi, - RetireUpdateDeleteStreamApi, + RetrieveUpdateDeleteStreamApi, ListCreateCommandApi, - RetireUpdateDeleteCommandApi, + RetrieveUpdateDeleteCommandApi, CreateSeasonApi, + ListClansApiView, ) urlpatterns = [ @@ -32,9 +40,29 @@ urlpatterns = [ path("", ListCreateEventApi.as_view(), name="list_create_event"), path( "", - RetireUpdateDeleteEventApi.as_view(), + RetrieveUpdateDeleteEventApi.as_view(), name="get_update_delete_event", ), + path( + "attendance/", + ListPlannedEvents.as_view(), + name="list_event_attendance", + ), + path( + "attendance//list/", + ListAttendedWorkersApi.as_view(), + name="list_event_attendance", + ), + path( + "attendance//submit/", + SubmitWorkerAttendedEvent.as_view(), + name="submit_event_attendance", + ), + path( + "attendance/", + RetrieveSubmitDeleteEventAttendance.as_view(), + name="get_submit_delete_event_attendance", + ), ] ), ), @@ -49,7 +77,7 @@ urlpatterns = [ ), path( "product/", - RetireUpdateDestroyProductApi.as_view(), + RetrieveUpdateDestroyProductApi.as_view(), name="get_update_destroy_product", ), ] @@ -62,7 +90,7 @@ urlpatterns = [ path("", ListCreateUserApi.as_view(), name="list_create_user"), path( "", - RetireUpdateDeleteUserApi.as_view(), + RetrieveUpdateDeleteUserApi.as_view(), name="get_update_delete_user", ), path( @@ -77,7 +105,7 @@ urlpatterns = [ ), path( "department/", - RetireUpdateDeleteDepartmentApi.as_view(), + RetrieveUpdateDeleteDepartmentApi.as_view(), name="get_update_delete_department", ), path( @@ -87,7 +115,7 @@ urlpatterns = [ ), path( "stream/", - RetireUpdateDeleteStreamApi.as_view(), + RetrieveUpdateDeleteStreamApi.as_view(), name="get_update_delete_stream", ), path( @@ -97,7 +125,7 @@ urlpatterns = [ ), path( "command/", - RetireUpdateDeleteCommandApi.as_view(), + RetrieveUpdateDeleteCommandApi.as_view(), name="get_update_delete_command", ), ] @@ -105,6 +133,11 @@ urlpatterns = [ ), path( "season/", - include([path("", CreateSeasonApi.as_view(), name="create new season")]), + include( + [ + path("", CreateSeasonApi.as_view(), name="create new season"), + path("clans/", ListClansApiView.as_view()), + ] + ), ), ] diff --git a/app/conf/settings/base.py b/app/conf/settings/base.py index 90623a9..ca88e06 100644 --- a/app/conf/settings/base.py +++ b/app/conf/settings/base.py @@ -62,7 +62,7 @@ DJANGO_APPS = [ ] THIRD_PARTY_APPS = ["rest_framework", "corsheaders", "django_celery_beat", "drf_yasg"] -LOCAL_APPS = ["users", "marketplace", "events"] +LOCAL_APPS = ["users", "marketplace", "events", "blockchain"] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/app/events/api/serializers.py b/app/events/api/serializers.py index 82d2429..f02d0dc 100644 --- a/app/events/api/serializers.py +++ b/app/events/api/serializers.py @@ -1,7 +1,9 @@ from rest_framework import serializers +from rest_framework.generics import get_object_or_404 -from events.models import Event +from events.models import Event, EventAttendance from users.api.serializers import UserSerializer +from users.models import User class EventSerializer(serializers.ModelSerializer): @@ -30,3 +32,45 @@ class EventSerializer(serializers.ModelSerializer): return Event.objects.create( **validated_data, creator=self.context["request"].user ) + + +class EventAttendanceSerializer(serializers.ModelSerializer): + class Meta: + model = EventAttendance + fields = ["id", "event_slug", "worker_username", "attended"] + extra_kwargs = { + "id": {"read_only": True}, + "event_slug": {"read_only": True}, + "worker_username": {"read_only": True}, + "attended": {"read_only": True}, + } + + def create(self, validated_data): + return EventAttendance.objects.get_or_create( + worker=self.context["request"].user, + event=get_object_or_404( + Event, slug=self.context["request"].parser_context["kwargs"]["slug"] + ), + )[0] + + +class SubmitUserAttendedSerializer(serializers.Serializer): + username = serializers.CharField(max_length=200) + + def create(self, validated_data): + event = get_object_or_404( + Event, slug=self.context["request"].parser_context["kwargs"]["slug"] + ) + ea = EventAttendance.objects.get_or_create( + event=event, + worker=get_object_or_404(User, username=validated_data["username"]), + )[0] + if not ea.attended: + ea.attended = True + ea.save() + ea.event.attended += 1 + ea.event.save() + return ea + + def update(self, instance, validated_data): + pass diff --git a/app/events/api/views.py b/app/events/api/views.py index 85e936c..aff181a 100644 --- a/app/events/api/views.py +++ b/app/events/api/views.py @@ -2,13 +2,19 @@ from datetime import datetime import pytz from rest_framework import generics +from rest_framework.exceptions import NotFound from rest_framework.generics import get_object_or_404 from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.permissions import IsAuthenticated +from rest_framework.views import APIView -from common.permissions import IsManager -from events.api.serializers import EventSerializer -from events.models import Event +from common.permissions import IsManager, IsWorker +from events.api.serializers import ( + EventSerializer, + EventAttendanceSerializer, + SubmitUserAttendedSerializer, +) +from events.models import Event, EventAttendance class ListCreateEventApi(generics.ListCreateAPIView): @@ -20,7 +26,7 @@ class ListCreateEventApi(generics.ListCreateAPIView): ) -class RetireUpdateDeleteEventApi(generics.RetrieveUpdateDestroyAPIView): +class RetrieveUpdateDeleteEventApi(generics.RetrieveUpdateDestroyAPIView): def get_object(self): event = get_object_or_404( Event, @@ -32,3 +38,51 @@ class RetireUpdateDeleteEventApi(generics.RetrieveUpdateDestroyAPIView): parser_classes = [FormParser, MultiPartParser] permission_classes = [IsAuthenticated, IsManager] queryset = Event.objects.all() + + +class RetrieveSubmitDeleteEventAttendance( + generics.CreateAPIView, generics.RetrieveDestroyAPIView +): + """Gets/Submits/Deletes that user is planing to go on event. Only works for worker""" + + def get_object(self): + event = get_object_or_404( + Event, + slug=self.request.parser_context["kwargs"]["slug"], + ) + if EventAttendance.objects.filter( + event=event, worker=self.request.user + ).exists(): + return EventAttendance.objects.get(event=event, worker=self.request.user) + raise NotFound + + serializer_class = EventAttendanceSerializer + permission_classes = [IsAuthenticated, IsWorker] + + +class ListPlannedEvents(generics.ListAPIView): + """Lists events that worker is planning to attend. Only works for worker""" + + def get_queryset(self): + return self.request.user.events.filter( + attended=False, + event__starts__gte=datetime.now(pytz.timezone("Europe/Moscow")), + ) + + serializer_class = EventAttendanceSerializer + permission_classes = [IsAuthenticated, IsWorker] + + +class ListAttendedWorkersApi(generics.ListAPIView): + def get_queryset(self): + return EventAttendance.objects.filter( + event__slug=self.request.parser_context["kwargs"]["slug"] + ) + + serializer_class = EventAttendanceSerializer + permission_classes = [IsAuthenticated, IsManager] + + +class SubmitWorkerAttendedEvent(generics.CreateAPIView): + serializer_class = SubmitUserAttendedSerializer + permission_classes = [IsAuthenticated, IsManager] diff --git a/app/events/models.py b/app/events/models.py index a2f6f51..9e51695 100644 --- a/app/events/models.py +++ b/app/events/models.py @@ -32,5 +32,20 @@ class EventAttendance(models.Model): token = models.CharField(blank=False, unique=True, max_length=128) attended = models.BooleanField(default=False) + @property + def event_slug(self): + return self.event.slug + + @property + def worker_username(self): + return self.worker.username + + def username(self): + return self.worker_username + def __str__(self): return f"{self.worker.name} attendance on {self.event.name}" + + class Meta: + ordering = ["event__starts"] + unique_together = ["event", "worker"] diff --git a/app/events/signals.py b/app/events/signals.py index e06e345..0c4f4e2 100644 --- a/app/events/signals.py +++ b/app/events/signals.py @@ -7,15 +7,16 @@ from utils.generators import generate_charset from .models import EventAttendance, Event -@receiver(pre_save, sender=EventAttendance) -def create_attendance(sender, instance, **kwargs): - token = generate_charset(25) - while EventAttendance.objects.filter(token=token).exists(): +@receiver(post_save, sender=EventAttendance) +def create_attendance(sender, instance, created, **kwargs): + if created: token = generate_charset(25) - instance.token = token + while EventAttendance.objects.filter(token=token).exists(): + token = generate_charset(25) + instance.token = token - instance.event.planning += 1 - instance.event.save(update_fileds=["planning"]) + instance.event.planning += 1 + instance.event.save(update_fields=["planning", "token"]) @receiver(post_save, sender=Event) @@ -38,4 +39,4 @@ def process_event(sender, instance, created, **kwargs): @receiver(post_delete, sender=EventAttendance) def delete_attendance(sender, instance, **kwargs): instance.event.planning -= 1 - instance.event.save(update_fileds=["planning"]) + instance.event.save(update_fields=["planning"]) diff --git a/app/marketplace/api/views.py b/app/marketplace/api/views.py index 8787a6e..1f4006f 100644 --- a/app/marketplace/api/views.py +++ b/app/marketplace/api/views.py @@ -15,7 +15,7 @@ class ListCreateProductApi(generics.ListCreateAPIView): queryset = Product.objects.all() -class RetireUpdateDestroyProductApi(generics.RetrieveUpdateDestroyAPIView): +class RetrieveUpdateDestroyProductApi(generics.RetrieveUpdateDestroyAPIView): serializer_class = ProductSerializer parser_classes = [FormParser, MultiPartParser] permission_classes = [IsAuthenticated, IsManager] diff --git a/app/users/api/serializers.py b/app/users/api/serializers.py index 3cf17e0..8105217 100644 --- a/app/users/api/serializers.py +++ b/app/users/api/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from ..services import create_season -from users.models import User +from users.models import User, Clan from users.models import User, Department, Stream, Command @@ -18,10 +18,12 @@ class UserSerializer(serializers.ModelSerializer): "wallet_public_key", "command", "department", + "clan_name", ] extra_kwargs = { "password": {"write_only": True}, "wallet_public_key": {"read_only": True}, + "clan_name": {"read_only": True}, "department": {"read_only": True}, } @@ -34,9 +36,10 @@ class UserSerializer(serializers.ModelSerializer): class CreateSeasonSerializer(serializers.Serializer): created = serializers.BooleanField(read_only=True) + def create(self, *args, **kwargs): create_season() - return {'created': True} + return {"created": True} class CommandSerializer(serializers.ModelSerializer): @@ -75,3 +78,11 @@ class DepartmentSerializer(serializers.ModelSerializer): "id": {"read_only": True}, "streams": {"read_only": True}, } + + +class ClanSerializer(serializers.ModelSerializer): + users = UserSerializer(many=True) + + class Meta: + model = Clan + fields = ["name", "users"] diff --git a/app/users/api/views.py b/app/users/api/views.py index 3fd6a6b..b41819d 100644 --- a/app/users/api/views.py +++ b/app/users/api/views.py @@ -3,14 +3,15 @@ from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from common.permissions import IsAdmin -from users.api.serializers import CreateSeasonSerializer from users.api.serializers import ( UserSerializer, DepartmentSerializer, StreamSerializer, CommandSerializer, + CreateSeasonSerializer, + ClanSerializer, ) -from users.models import User, Department, Stream, Command +from users.models import User, Department, Stream, Command, Clan class ListCreateUserApi(generics.ListCreateAPIView): @@ -24,7 +25,7 @@ class CreateSeasonApi(generics.CreateAPIView): # permission_classes = [IsAuthenticated, IsAdmin] -class RetireUpdateDeleteUserApi(generics.RetrieveUpdateDestroyAPIView): +class RetrieveUpdateDeleteUserApi(generics.RetrieveUpdateDestroyAPIView): def get_object(self): user = get_object_or_404( User, @@ -43,7 +44,7 @@ class ListCreateDepartmentApi(generics.ListCreateAPIView): queryset = Department.objects.all() -class RetireUpdateDeleteDepartmentApi(generics.RetrieveUpdateDestroyAPIView): +class RetrieveUpdateDeleteDepartmentApi(generics.RetrieveUpdateDestroyAPIView): lookup_field = "pk" serializer_class = DepartmentSerializer permission_classes = [IsAuthenticated, IsAdmin] @@ -55,7 +56,7 @@ class ListCreateStreamApi(ListCreateDepartmentApi): queryset = Stream.objects.all() -class RetireUpdateDeleteStreamApi(RetireUpdateDeleteDepartmentApi): +class RetrieveUpdateDeleteStreamApi(RetrieveUpdateDeleteDepartmentApi): serializer_class = StreamSerializer queryset = Stream.objects.all() @@ -65,6 +66,12 @@ class ListCreateCommandApi(ListCreateDepartmentApi): queryset = Command.objects.all() -class RetireUpdateDeleteCommandApi(RetireUpdateDeleteDepartmentApi): +class RetrieveUpdateDeleteCommandApi(RetrieveUpdateDeleteDepartmentApi): serializer_class = CommandSerializer queryset = Command.objects.all() + + +class ListClansApiView(generics.ListAPIView): + serializer_class = ClanSerializer + queryset = Clan.objects.all() + permission_classes = [IsAuthenticated, IsAdmin] diff --git a/app/users/models.py b/app/users/models.py index 15b54f1..31b2ae0 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -22,7 +22,7 @@ class User(AbstractUser): type = models.CharField( max_length=6, choices=WorkerType.choices, default=WorkerType.WORKER ) - clan = models.ForeignKey("users.Clan", on_delete=models.SET_NULL, null=True) + clan = models.ForeignKey("users.Clan", related_name="users", on_delete=models.SET_NULL, null=True) command = models.ForeignKey( "users.Command", related_name="workers", on_delete=models.CASCADE ) @@ -47,6 +47,10 @@ class User(AbstractUser): def department(self): return self.command.stream.department.name + @property + def clan_name(self): + return self.clan.name + class Meta: ordering = ["-id"] diff --git a/app/users/services.py b/app/users/services.py index 22b85fa..c2e6d9f 100644 --- a/app/users/services.py +++ b/app/users/services.py @@ -12,10 +12,10 @@ def end_season(): mx_value = -1 mx_clan = None for clan in Clan.objects.all(): - if sum(map(lambda user: user.respect, clan.user_set.all())) > mx_value: - mx_value = sum(map(lambda user: user.respect, clan.user_set.all())) + if sum(map(lambda user: user.respect, clan.users.all())) > mx_value: + mx_value = sum(map(lambda user: user.respect, clan.users.all())) mx_clan = clan - for user in mx_clan.user_set.all(): + for user in mx_clan.users.all(): transfer_rubbles( settings.MAIN_WALLET, user.wallet_public_key, @@ -25,7 +25,7 @@ def end_season(): def create_chat(clan: Clan): - user_list = list(map(lambda user: user.telegram, clan.user_set.all())) + user_list = list(map(lambda user: user.telegram, clan.users.all())) if len(user_list): r.post( f"{settings.TELEGRAM_API}/create-chat", @@ -38,15 +38,13 @@ def create_season(): if len(Clan.objects.all()): end_season() - users = list(User.objects.all()) + users = list(User.objects.filter(type=User.WorkerType.WORKER)) shuffle(users) clan = None for index, user in enumerate(users): - if (index % 10 == 0) or (index == len(users) - 1): - if clan is not None: - create_chat(clan) + if index % 5 == 0: clan = Clan.objects.create() user.clan = clan user.save() - if len(users) % 10 != 0: + for clan in Clan.objects.all(): create_chat(clan) diff --git a/app/users/signals.py b/app/users/signals.py index 9b96108..bb02d58 100644 --- a/app/users/signals.py +++ b/app/users/signals.py @@ -5,15 +5,13 @@ from users.models import User from utils.blockchain import create_wallet -@receiver(pre_save, sender=User) -def create_user(sender, instance, **kwargs): - wallet = create_wallet() - instance.wallet_public_key = wallet.publicKey - instance.wallet_private_key = wallet.privateKey - - @receiver(post_save, sender=User) def process_user(sender, instance, created, **kwargs): if created: instance.set_password(instance.password) + + wallet = create_wallet() + + instance.wallet_public_key = wallet.publicKey + instance.wallet_private_key = wallet.privateKey instance.save()