diff --git a/api/admin.py b/api/admin.py index d525d39..ef8a802 100644 --- a/api/admin.py +++ b/api/admin.py @@ -16,6 +16,9 @@ models.Categorie, models.Client, models.Order, - models.Participant) + models.Participant, + models.Compostage, + models.File, + ) class BasicAdmin(admin.ModelAdmin): pass diff --git a/api/migrations/0012_compostage_file.py b/api/migrations/0012_compostage_file.py new file mode 100644 index 0000000..6bb9543 --- /dev/null +++ b/api/migrations/0012_compostage_file.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-12-14 17:00 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0011_auto_20171211_1858'), + ] + + operations = [ + + migrations.CreateModel( + name='File', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nom', models.CharField(max_length=100)), + ('active', models.BooleanField(default=False)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Event')), + ('file_parente', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.File')), + ('product_type', models.ManyToManyField(blank=True, null=True, to='api.Product')), + ], + ), + ] diff --git a/api/migrations/0013_auto_20171214_1801.py b/api/migrations/0013_auto_20171214_1801.py new file mode 100644 index 0000000..771babd --- /dev/null +++ b/api/migrations/0013_auto_20171214_1801.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-12-14 17:01 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0012_compostage_file'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='product_type', + field=models.ManyToManyField(blank=True, to='api.Product'), + ), + ] diff --git a/api/migrations/0014_auto_20171214_1808.py b/api/migrations/0014_auto_20171214_1808.py new file mode 100644 index 0000000..957e166 --- /dev/null +++ b/api/migrations/0014_auto_20171214_1808.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-12-14 17:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0013_auto_20171214_1801'), + ] + + operations = [ + migrations.AddField( + model_name='file', + name='option_type', + field=models.ManyToManyField(blank=True, to='api.Option'), + ), + migrations.AddField( + model_name='file', + name='validation_type', + field=models.IntegerField(choices=[(0, 'Aucun mode défini'), (1, 'Validation de produits'), (2, "Validation d'options")], default=0), + preserve_default=False, + ), + ] diff --git a/api/migrations/0015_auto_20171214_1808.py b/api/migrations/0015_auto_20171214_1808.py new file mode 100644 index 0000000..95442ed --- /dev/null +++ b/api/migrations/0015_auto_20171214_1808.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-12-14 17:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0014_auto_20171214_1808'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='validation_type', + field=models.IntegerField(choices=[(0, 'Aucun mode défini'), (1, 'Validation de produits'), (2, "Validation d'options")], default=0), + ), + ] diff --git a/api/migrations/0016_auto_20171214_1821.py b/api/migrations/0016_auto_20171214_1821.py new file mode 100644 index 0000000..1152dfb --- /dev/null +++ b/api/migrations/0016_auto_20171214_1821.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-12-14 17:21 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0015_auto_20171214_1808'), + ] + + operations = [ + migrations.CreateModel( + name='Compostage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_created=True)), + ('billet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Billet')), + ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.File')), + ], + ), + migrations.AddField( + model_name='billet', + name='files', + field=models.ManyToManyField(blank=True, through='api.Compostage', to='api.File'), + ), + ] diff --git a/api/models.py b/api/models.py index 99ba0ea..6e3b20d 100644 --- a/api/models.py +++ b/api/models.py @@ -19,6 +19,39 @@ ) +class File(models.Model): + """ + Représente un file de lecture de billet + """ + VALIDATION_MODES = ( + (0, "Aucun mode défini"), + (1, "Validation de produits"), + (2, "Validation d'options"), + ) + + nom = models.CharField(max_length=100) + event = models.ForeignKey('Event') + active = models.BooleanField(default=False) + file_parente = models.ForeignKey('File', null=True, blank=True) + product_type = models.ManyToManyField('Product', blank=True) + option_type = models.ManyToManyField('Option', blank=True) + validation_type = models.IntegerField(choices=VALIDATION_MODES, default=0) + + def __str__(self): + return "File {}".format(self.nom) + +class Compostage(models.Model): + """ + Fait la liaison entre les files et les billets. + """ + billet = models.ForeignKey("Billet") + file = models.ForeignKey("File") + date = models.DateTimeField(auto_created=True,auto_now_add=True) + + def __str__(self): + return "Billet "+ str(self.billet.id) +" validé à la file "+ str(self.file.nom) + + class Membership(models.Model): LEVEL_ADMIN = 0 LEVEL_MANAGER = 100 @@ -134,7 +167,7 @@ def reserved_units(self, billets=None): return billets.filter(product=self).aggregate(total=Count('id'))['total'] if type(self) is Option: return BilletOption.objects.filter(billet__in=billets, option=self) \ - .aggregate(total=Sum('amount'))['total'] or 0 + .aggregate(total=Sum('amount'))['total'] or 0 def reserved_seats(self, billets=None): return self.reserved_units(billets) * (self.seats or 1) @@ -241,6 +274,7 @@ class Billet(models.Model): product = models.ForeignKey(Product, null=True, blank=True, related_name='billets') options = models.ManyToManyField(Option, through=BilletOption, related_name='billets') order = models.ForeignKey('Order', null=True, related_name='billets') + files = models.ManyToManyField(File,through='Compostage',blank=True) @staticmethod def validated(): @@ -347,7 +381,6 @@ def __str__(self): class Answer(models.Model): - order = models.ForeignKey('Order', related_name='answers') question = models.ForeignKey(Question) participant = models.ForeignKey(Participant, null=True, blank=True) @@ -434,7 +467,7 @@ class Meta: updated_at = models.DateTimeField(auto_now=True) client = models.ForeignKey(Client, blank=True, null=True, related_name="orders") status = models.IntegerField(verbose_name=_('status'), default=0, choices=STATUSES) - transaction = models.ForeignKey(TransactionRequest,default=None, null=True) + transaction = models.ForeignKey(TransactionRequest, default=None, null=True) event = models.ForeignKey(Event) def destroy_all(self): @@ -508,5 +541,3 @@ def update_order_on_card_transaction(instance, **kwargs): order.save() except Order.DoesNotExist: pass - - diff --git a/api/serializers.py b/api/serializers.py index 573e86b..59cf31b 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -3,9 +3,15 @@ from rest_framework.relations import PrimaryKeyRelatedField from api import models -from api.models import Billet, Product, Option +from api.models import Billet, Product, Option, Compostage +class CompostageSerializer(serializers.ModelSerializer): + class Meta: + model = Compostage + fields = ("id","billet","file") + depth = 0 + class UserSerializer(serializers.ModelSerializer): class Meta: model = User diff --git a/api/urls.py b/api/urls.py index 4da1067..2b5abaa 100644 --- a/api/urls.py +++ b/api/urls.py @@ -10,6 +10,7 @@ router.register(r'options', views.OptionViewSet, 'options') router.register(r'billets', views.BilletViewSet, 'billet') router.register(r'order', views.OrderViewSet, 'orders') +router.register(r'compostages',views.CompostageViewSet,'compostages') admin_router = routers.SimpleRouter() admin_router.register(r'events', admin.EventViewSet, 'events') @@ -19,6 +20,7 @@ # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ + url(r'^billetcheck/', views.billet_check), url(r'^authenticate/invitation$', views.InvitationAuthentication.as_view()), url(r'^authenticate$', obtain_jwt_token), url(r'^authenticate/refresh$', refresh_jwt_token), diff --git a/api/views.py b/api/views.py index a1b2c11..1dd88fa 100644 --- a/api/views.py +++ b/api/views.py @@ -1,24 +1,25 @@ from datetime import datetime, timedelta from django.core.exceptions import ValidationError -from django.core.signing import TimestampSigner +from django.core.signing import TimestampSigner, Signer from django.db import transaction from django.utils.decorators import method_decorator from django.utils import timezone from django.views import View from django.views.decorators.csrf import csrf_exempt from rest_framework import viewsets, status -from rest_framework.decorators import detail_route, authentication_classes, permission_classes +from rest_framework.decorators import detail_route, authentication_classes, permission_classes, api_view from rest_framework.exceptions import APIException -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_jwt.settings import api_settings from api import permissions -from api.models import Event, Order, Option, Product, Billet, Categorie, Invitation, Client, BilletOption +from api.models import Event, Order, Option, Product, Billet, Categorie, Invitation, File, BilletOption, Compostage +from api.permissions import IsEventManager from api.serializers import BilletSerializer, CategorieSerializer, InvitationSerializer, ParticipantSerializer, \ - AnswerSerializer, BilletOptionSerializer, BilletOptionInputSerializer, UserSerializer + AnswerSerializer, BilletOptionSerializer, BilletOptionInputSerializer, UserSerializer, CompostageSerializer from mercanet.models import TransactionRequest from .serializers import EventSerializer, OrderSerializer, OptionSerializer, \ ProductSerializer @@ -27,6 +28,43 @@ plus_disponible_view = Response("Ce que vous demandez n'est plus disponible", status=status.HTTP_200_OK) invalid_request_view = Response("Requête invalide, les paramètres spécifiés dans le POST sont non conformes", status=status.HTTP_400_BAD_REQUEST) +@csrf_exempt +@api_view(['GET','POST']) +@permission_classes((AllowAny,)) +@detail_route(methods=["POST"]) +def billet_check(request): + """ + Prend en paramètre POST id l'id signé du billet. Nécessite d'être authentifié en tant que manager de l'évenement + + :return: Le billet si il existe. + """ + data = request.data['id'] + id = Signer().unsign(data) + + return Response(BilletSerializer(Billet.objects.get(id=id)).data) + +class CompostageViewSet(viewsets.ModelViewSet): + """ + Le viewset pour les compostages: @see le model Compostage + """ + queryset = Compostage.objects.all() + serializer_class = CompostageSerializer + permission_classes = [AllowAny] + + def create(self, request, *args, **kwargs): + try: + file = File.objects.get(id=request.data['file']) + + if Compostage.objects.filter(billet=request.data["billet"], file=file.id).count()>0: + return Response("Attention ! Ce billet a déja été validé") + + compostage = Compostage(billet=Billet.objects.get(id=request.data["billet"]),file=file) + compostage.save() + return Response(CompostageSerializer(compostage).data) + except File.DoesNotExist as e: + return Response("La file demandée n'existe pas !") + except Billet.DoesNotExist as e: + return Response("le billet demandé n'existe pas !") class EventsViewSet(viewsets.ModelViewSet):