diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2ceb1512b7cdaafb6b965eb3f2ffe9ea998096d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +django +djangorestframework +django-cors-headers +coreapi diff --git a/uploadexample/api/__init__.py b/uploadexample/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uploadexample/api/apps.py b/uploadexample/api/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..d87006dd60ff626eefdf7f8ffaa3998d7f99615a --- /dev/null +++ b/uploadexample/api/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + name = 'api' diff --git a/uploadexample/upload/serializers.py b/uploadexample/api/serializers.py similarity index 62% rename from uploadexample/upload/serializers.py rename to uploadexample/api/serializers.py index d4fd0a7404557456448c4d0ac1a520b190e9c1bc..379cfe260795e2df2bfcaa9b922ef08f9d646e40 100644 --- a/uploadexample/upload/serializers.py +++ b/uploadexample/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import StudentRegistration +from upload.models import StudentRegistration from .validators import FileValidator @@ -7,11 +7,15 @@ class StudentRegistrationSerializer(serializers.ModelSerializer): class Meta: model = StudentRegistration - fields = ('first_name', 'last_name', 'image_agreement') + fields = ('id', 'first_name', 'last_name', 'image_agreement', + 'submission_date',) extra_kwargs = { 'image_agreement': { 'validators': [ FileValidator(allowed_mimetypes=('application/pdf',)), ], + }, + 'submission_date': { + 'format': '%Y-%m-%d', } } diff --git a/uploadexample/api/urls.py b/uploadexample/api/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb5f8e61a5494efee0c26a9599a78c70b4f605e --- /dev/null +++ b/uploadexample/api/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from rest_framework.routers import DefaultRouter +from rest_framework.documentation import include_docs_urls +from . import views + + +urlpatterns = [ + path('docs', include_docs_urls(title='API')) +] + +router = DefaultRouter() +router.register(r'registrations', views.RegistrationViewSet) +urlpatterns += router.urls diff --git a/uploadexample/upload/validators.py b/uploadexample/api/validators.py similarity index 100% rename from uploadexample/upload/validators.py rename to uploadexample/api/validators.py diff --git a/uploadexample/api/views.py b/uploadexample/api/views.py new file mode 100644 index 0000000000000000000000000000000000000000..6680aace614f9b9b820f6e032d2275c23e0c827f --- /dev/null +++ b/uploadexample/api/views.py @@ -0,0 +1,13 @@ +from rest_framework import viewsets +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin) +from upload.models import StudentRegistration +from .serializers import StudentRegistrationSerializer + + +class RegistrationViewSet(ListModelMixin, + CreateModelMixin, + DestroyModelMixin, + viewsets.GenericViewSet): + queryset = StudentRegistration.objects.all() + serializer_class = StudentRegistrationSerializer diff --git a/uploadexample/upload/admin.py b/uploadexample/upload/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/uploadexample/upload/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/uploadexample/upload/management/commands/clean_media.py b/uploadexample/upload/management/commands/clean_media.py index 654f1a3b7218ad0b36244bbe1b2faec96b7ebdd0..4173b59c8a1f14b1f02cd60bf75f51024aff6ead 100644 --- a/uploadexample/upload/management/commands/clean_media.py +++ b/uploadexample/upload/management/commands/clean_media.py @@ -77,7 +77,7 @@ class Command(BaseCommand): if deletables: unused = len(deletables) self.stdout.write('Detected unused media files:') - self.stdout.write(*('\t' + f_ for f_ in deletables)) + self.stdout.write('\n'.join(f_ for f_ in deletables)) for file_ in deletables: os.remove(os.path.join(self.media_root, file_)) diff --git a/uploadexample/upload/models.py b/uploadexample/upload/models.py index 432286dadb6697c3f1ce33722af380c836224971..5b1f81faa9fed0e3aaa7154ca62d7e440c654081 100644 --- a/uploadexample/upload/models.py +++ b/uploadexample/upload/models.py @@ -25,3 +25,6 @@ class StudentRegistration(models.Model): verbose_name = "dossier d'inscription" verbose_name_plural = "dossiers d'inscription" ordering = ('-submission_date',) + + def __str__(self): + return '{} {}'.format(self.first_name, self.last_name) diff --git a/uploadexample/upload/static/css/registration.css b/uploadexample/upload/static/css/registration.css index 77ca4247e9f0a3d9d9abc3afb418bd0b8eff6a72..6bc7f69d3c9961890dd88ef395ad0cf455a09cf7 100644 --- a/uploadexample/upload/static/css/registration.css +++ b/uploadexample/upload/static/css/registration.css @@ -1,9 +1,19 @@ +@import url('https://fonts.googleapis.com/css?family=Nunito:400,700,900'); +@import url('https://fonts.googleapis.com/css?family=Raleway:400,700,900'); + body { - margin: 2em; + padding: 2em; + font-family: "Nunito", sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Raleway", sans-serif; + font-weight: bolder; } + .content { - margin: 0 auto; + margin: 2em auto; max-width: 500px; } @@ -14,7 +24,12 @@ body { } .list li { - padding: .5em 1em; + padding: 1em; margin: 1em 0; background: #eee; + display: flex; +} + +.list li .right { + margin-left: auto; } diff --git a/uploadexample/upload/templates/upload/base.html b/uploadexample/upload/templates/upload/base.html index 65cbb908ffd7e2795da5ea3050624116baa59472..324a7b856a29bb971a198b1750bd21759d123160 100644 --- a/uploadexample/upload/templates/upload/base.html +++ b/uploadexample/upload/templates/upload/base.html @@ -6,19 +6,19 @@ <title>OSER</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> <link rel="stylesheet" href="{% static 'css/registration.css' %}"> </head> <body> <nav> - <ul class="nav nav-tabs"> - <li> - <a href="{% url 'registration-list' %}">Liste des inscriptions</a> + <ul class="nav nav-tabs justify-content-center"> + <li class="nav-item"> + <a class="nav-link" href="{% url 'registration-list' %}">Liste des inscriptions</a> </li> - <li> - <a href="{% url 'registration-create' %}">Nouvelle inscription</a> + <li class="nav-item"> + <a class="nav-link" href="{% url 'registration-create' %}">Nouvelle inscription</a> </li> </ul> </nav> @@ -28,8 +28,8 @@ <div class="content"> {% block content %}{% endblock %} </div> - <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script> </body> </html> diff --git a/uploadexample/upload/templates/upload/registration_list.html b/uploadexample/upload/templates/upload/registration_list.html deleted file mode 100644 index 48edf497adbf075500b11b62f8f0c3724ccf2f60..0000000000000000000000000000000000000000 --- a/uploadexample/upload/templates/upload/registration_list.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "upload/base.html" %} - -{% block content %} - <h1>Dossiers d'inscriptions</h1> - - <a href="{% url 'registration-create' %}" class="btn btn-primary">Nouvelle inscription</a> - - <ul class="list"> - {% for registration in registrations %} - <li> - <div class="row"> - <div class="col-xs-10"> - <h4>{{ registration.first_name }} {{ registration.last_name }}</h4> - <p class="text-muted">Soumis le {{ registration.submission_date }}</p> - <br/> - {% if registration.image_agreement %} - <a href="{{ registration.image_agreement.url }}">Droit à l'image</a> - {% endif %} - </div> - <div class="col-xs-2"> - <span class="pull-right"> - <a class="btn btn-danger" href="{% url 'registration-delete' pk=registration.pk %}">Supprimer</a> - </span> - </div> - </div> - <br /> - </li> - {% endfor %} - </ul> -{% endblock %} diff --git a/uploadexample/upload/templates/upload/studentregistration_confirm_delete.html b/uploadexample/upload/templates/upload/studentregistration_confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..28c30889d5452da25f4770818c2812f4c3bfeee9 --- /dev/null +++ b/uploadexample/upload/templates/upload/studentregistration_confirm_delete.html @@ -0,0 +1,10 @@ +{% extends "upload/base.html" %} + +{% block content %} +<h2>Confirmation</h2> +<form action="" method="post"> + {% csrf_token %} + <p>Êtes-vous sûrs de vouloir supprimer le dossier de {{ object }}?</p> + <input type="submit" class="btn btn-danger" value="Oui, supprimer" /> +</form> +{% endblock %} diff --git a/uploadexample/upload/templates/upload/studentregistration_form.html b/uploadexample/upload/templates/upload/studentregistration_form.html new file mode 100644 index 0000000000000000000000000000000000000000..431b7c3d89723e915645fe88a8d416596238b70e --- /dev/null +++ b/uploadexample/upload/templates/upload/studentregistration_form.html @@ -0,0 +1,13 @@ +{% extends "upload/base.html" %} +{% load rest_framework %} + +{% block content %} +<h1>Inscription au tutorat</h1> + +<!-- Use multipart/form-data encoding to allow sending files --> +<form action="{% url 'registration-create' %}" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form.as_p }} + <input type="submit" class="btn btn-success" value="Envoyer"> +</form> +{% endblock %} diff --git a/uploadexample/upload/templates/upload/studentregistration_list.html b/uploadexample/upload/templates/upload/studentregistration_list.html new file mode 100644 index 0000000000000000000000000000000000000000..2642a48ed7431b7a286a532904080bdc71fb744a --- /dev/null +++ b/uploadexample/upload/templates/upload/studentregistration_list.html @@ -0,0 +1,27 @@ +{% extends "upload/base.html" %} + +{% block content %} + <h1>Dossiers d'inscriptions</h1> + + <a href="{% url 'registration-create' %}" class="btn btn-primary">Nouvelle inscription</a> + + <ul class="list"> + {% for registration in registrations %} + <li> + <div> + <h4>{{ registration.first_name }} {{ registration.last_name }}</h4> + <p class="text-muted">Soumis le {{ registration.submission_date }}</p> + <br/> + {% if registration.image_agreement %} + <a href="{{ registration.image_agreement.url }}">Droit à l'image</a> + {% endif %} + </div> + <div class="right"> + <span class="pull-right"> + <a class="btn btn-danger" href="{% url 'registration-delete' pk=registration.pk %}">Supprimer</a> + </span> + </div> + </li> + {% endfor %} + </ul> +{% endblock %} diff --git a/uploadexample/upload/tests.py b/uploadexample/upload/tests.py deleted file mode 100644 index 7ce503c2dd97ba78597f6ff6e4393132753573f6..0000000000000000000000000000000000000000 --- a/uploadexample/upload/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/uploadexample/upload/urls.py b/uploadexample/upload/urls.py index fdfcad6821d5a3893f0fb8fd0c944f4372a5df64..b5b3e70170e6a2bbb6848c7cfe1756e3cd60c79d 100644 --- a/uploadexample/upload/urls.py +++ b/uploadexample/upload/urls.py @@ -7,5 +7,5 @@ urlpatterns = [ path('inscription', RegistrationCreate.as_view(), name='registration-create'), path('inscription/<int:pk>', RegistrationDelete.as_view(), - name='registration-delete') + name='registration-delete'), ] diff --git a/uploadexample/upload/views.py b/uploadexample/upload/views.py index 559bd9165dd47578b36440fe091dbe8607f0c3a8..12d6dde60d26eddad297702b7f1ba43da06650a0 100644 --- a/uploadexample/upload/views.py +++ b/uploadexample/upload/views.py @@ -1,48 +1,52 @@ -from django.shortcuts import redirect, get_object_or_404 +"""Regular Django views for a template-based frontend.""" +from django.urls import reverse_lazy from django.contrib import messages -from rest_framework.response import Response -from rest_framework.views import APIView -from rest_framework.renderers import TemplateHTMLRenderer +from django.views.generic import ListView, CreateView, DeleteView from .models import StudentRegistration -from .serializers import StudentRegistrationSerializer # Create your views here. -class RegistrationCreate(APIView): - renderer_classes = [TemplateHTMLRenderer] - template_name = 'upload/registration_create.html' +class RegistrationList(ListView): + """List of the registrations.""" - def get(self, request): - registration = StudentRegistration() - serializer = StudentRegistrationSerializer(registration) - return Response({'serializer': serializer}) + model = StudentRegistration + context_object_name = 'registrations' - def post(self, request): - registration = StudentRegistration() - serializer = StudentRegistrationSerializer( - registration, data=request.data) - if not serializer.is_valid(): - return Response({'serializer': serializer}) - serializer.save() - messages.success(request, 'Inscription réussie') - return redirect('registration-list') +class RegistrationCreate(CreateView): + """Create a new registration.""" -class RegistrationList(APIView): - renderer_classes = [TemplateHTMLRenderer] - template_name = 'upload/registration_list.html' + model = StudentRegistration + fields = ('first_name', 'last_name', 'image_agreement') + success_url = reverse_lazy('registration-list') - def get(self, request): - queryset = StudentRegistration.objects.all() - return Response({'registrations': queryset}) + def get_form(self, **kwargs): + """Customize style by adding Bootstrap classes.""" + form = super().get_form(**kwargs) + classes = { + 'first_name': 'form-control', + 'last_name': 'form-control', + 'image_agreement': 'form-control-file', + } + for field in form.fields: + form.fields[field].widget.attrs.update({'class': classes[field]}) + return form + def form_valid(self, form): + response = super().form_valid(form) + messages.success(self.request, 'Inscription réussie') + return response -class RegistrationDelete(APIView): - def get(self, request, pk): - registration = get_object_or_404(StudentRegistration, pk=pk) - registration.delete() +class RegistrationDelete(DeleteView): + """Delete a registration after confirmation.""" + + model = StudentRegistration + success_url = reverse_lazy('registration-list') + + def delete(self, request, *args, **kwargs): + response = super().delete(request, *args, **kwargs) messages.success(request, 'Inscription supprimée') - return redirect('registration-list') + return response diff --git a/uploadexample/uploadexample/settings.py b/uploadexample/uploadexample/settings.py index 02abfe5a902938cdbd30e9d4841c3bcea9ab857c..27c71663b119c7c70c0d0f390f06e31dcaa081c4 100644 --- a/uploadexample/uploadexample/settings.py +++ b/uploadexample/uploadexample/settings.py @@ -47,12 +47,14 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', + 'corsheaders', 'upload', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -60,6 +62,10 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +CORS_ORIGIN_WHITELIST = ( + 'localhost:4200', +) + ROOT_URLCONF = 'uploadexample.urls' TEMPLATES = [ diff --git a/uploadexample/uploadexample/urls.py b/uploadexample/uploadexample/urls.py index 8f2e314bfaf4c6972d2031c8ca3a7485a1ed5aa8..6b137d52af802d7b7ec0bf009172abf0aed5f1ec 100644 --- a/uploadexample/uploadexample/urls.py +++ b/uploadexample/uploadexample/urls.py @@ -21,6 +21,7 @@ from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('upload.urls')), + path('api/', include('api.urls')), ] if settings.DEBUG: diff --git a/uploadfront b/uploadfront new file mode 160000 index 0000000000000000000000000000000000000000..7d638632964e91a9c1667431fd7071aa4d142b2e --- /dev/null +++ b/uploadfront @@ -0,0 +1 @@ +Subproject commit 7d638632964e91a9c1667431fd7071aa4d142b2e