diff --git a/benevalibre/instance/context_processors.py b/benevalibre/instance/context_processors.py new file mode 100644 index 0000000..0c62a99 --- /dev/null +++ b/benevalibre/instance/context_processors.py @@ -0,0 +1,6 @@ +from .models import TermsOfUse + + +def terms(request): + obj = TermsOfUse.objects.order_by('pk').last() + return {'terms': {'file': obj}} diff --git a/benevalibre/instance/forms.py b/benevalibre/instance/forms.py index e1b3a3d..7be6bb2 100644 --- a/benevalibre/instance/forms.py +++ b/benevalibre/instance/forms.py @@ -43,3 +43,9 @@ class InstanceCategoryCreateForm(CustomTapeformMixin, forms.ModelForm): class InstanceCategoryUpdateForm(InstanceCategoryCreateForm): pass + + +class TermsOfUseCreateForm(CustomTapeformMixin, forms.ModelForm): + class Meta: + model = models.TermsOfUse + fields = '__all__' diff --git a/benevalibre/instance/migrations/0006_termsofuse.py b/benevalibre/instance/migrations/0006_termsofuse.py new file mode 100644 index 0000000..d7c69ce --- /dev/null +++ b/benevalibre/instance/migrations/0006_termsofuse.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.24 on 2021-09-02 15:03 + +import benevalibre.instance.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('instance', '0005_defaultrole_verbose_name'), + ] + + operations = [ + migrations.CreateModel( + name='TermsOfUse', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.FileField(help_text='Format attendu : pdf', upload_to='terms', validators=[benevalibre.instance.validators.validate_is_pdf], verbose_name='Charte')), + ], + options={'verbose_name': "charte d'utilisation", 'verbose_name_plural': "chartes d'utilisation"}, + ), + ] diff --git a/benevalibre/instance/models.py b/benevalibre/instance/models.py index 0eee837..d3ad471 100644 --- a/benevalibre/instance/models.py +++ b/benevalibre/instance/models.py @@ -6,6 +6,8 @@ import reversion from benevalibre.utils.mixins import HTMLDocString from benevalibre.utils.models import AbstractRole, AbstractTaxonomy +from .validators import validate_is_pdf + @reversion.register() class InstanceCategory(HTMLDocString, AbstractTaxonomy): @@ -141,3 +143,25 @@ class DefaultRole(HTMLDocString, AbstractRole): "Vous devez en disposer d'un pour permettre l'inscription d'un " "bénévole." ) + + +class TermsOfUse(models.Model): + """ + La charte d'utilisation de la plateforme. + + Une fois définie elle apparait à tous les utilisateurs via le menu + principal de la plateforme. Le format attendu est un pdf. + + Pour mettre à jour la charte il suffit de téléverser un nouveau document. + """ + + class Meta: + verbose_name = "charte d'utilisation" + verbose_name_plural = "chartes d'utilisation" + + file = models.FileField( + 'Charte', + upload_to='terms', + validators=[validate_is_pdf], + help_text="Format attendu : pdf", + ) diff --git a/benevalibre/instance/tests/test_views.py b/benevalibre/instance/tests/test_views.py index cf747d9..04e8310 100644 --- a/benevalibre/instance/tests/test_views.py +++ b/benevalibre/instance/tests/test_views.py @@ -1,5 +1,7 @@ +from io import BytesIO + from django.contrib.messages import get_messages -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy import pytest from bs4 import BeautifulSoup as bs @@ -255,3 +257,45 @@ class TestDefaultLevel(StaffOnlyViewMixin): messages = [m.message for m in get_messages(response.wsgi_request)] assert len(messages) == 1 assert "« niveau de lutte » a été supprimé" in messages[0] + + +@pytest.mark.django_db +class TestTermsOfUsePage: + def test_terms_page_redirects_not_logged_users(self, client): + response = client.get(reverse('instance:terms_of_use')) + assert response.status_code == 302 + assert response.url == "/account/login/?next=/instance/charte/" + + def test_terms_page_displays_403_to_non_superusers(self, client, user): + client.force_login(user) + response = client.get(reverse('instance:terms_of_use')) + assert response.status_code == 403 + + def test_superusers_access_terms_page(self, client, superuser): + client.force_login(superuser) + response = client.get(reverse('instance:terms_of_use')) + assert "Téléverser une nouvelle charte" in response.content.decode() + + @pytest.mark.parametrize( + 'content, name, expected', + ( + ('test', 'test.txt', 200), + ('%PDF-1.5', 'test.txt', 200), + ('test', 'test.pdf', 200), + ('%PDF-1.5', 'test.pdf', 302), + ), + ) + def test_terms_of_use_validation( + self, client, superuser, content, name, expected + ): + client.force_login(superuser) + _file = BytesIO(content.encode()) + _file.name = name + response = client.post( + reverse('instance:terms_of_use'), data={'file': _file} + ) + assert response.status_code == expected + if expected == 302: + messages = [m.message for m in get_messages(response.wsgi_request)] + assert len(messages) == 1 + assert "est désormais en ligne." in messages[0] diff --git a/benevalibre/instance/urls.py b/benevalibre/instance/urls.py index e3e3eb4..799eb8f 100644 --- a/benevalibre/instance/urls.py +++ b/benevalibre/instance/urls.py @@ -13,5 +13,6 @@ urlpatterns = ( path( 'instance-category/', crud_include(views, 'InstanceCategory', 'instance_category'), - ) + ), + path('charte/', views.TermsOfUseCreate.as_view(), name='terms_of_use'), ) diff --git a/benevalibre/instance/validators.py b/benevalibre/instance/validators.py new file mode 100644 index 0000000..d3f6b17 --- /dev/null +++ b/benevalibre/instance/validators.py @@ -0,0 +1,20 @@ +import os + +from django.core.exceptions import ValidationError + +import magic + + +def validate_is_pdf(file): + validation_error = ValidationError( + 'Veuillez sélectionner un fichier au format PDF.' + ) + valid_mime_types = ['application/pdf'] + file_mime_type = magic.from_buffer(file.read(1024), mime=True) + if file_mime_type not in valid_mime_types: + raise validation_error + + valid_file_extensions = ['.pdf'] + _, ext = os.path.splitext(file.name) + if ext.lower() not in valid_file_extensions: + raise validation_error diff --git a/benevalibre/instance/views.py b/benevalibre/instance/views.py index 9ab4e4f..3966d25 100644 --- a/benevalibre/instance/views.py +++ b/benevalibre/instance/views.py @@ -138,3 +138,20 @@ class DefaultCategoryDelete( DefaultCategoryViewMixin, views.CruditorDeleteView ): pass + + +# CHART OF USE DEFINITION +# ------------------------------------------------------------------------------ + + +class TermsOfUseCreate( + StaffCruditorPageMixin, + views.CruditorAddView, +): + object = None + success_url = reverse_lazy('base:home') + title = "Téléverser une nouvelle charte d'utilisation" + form_class = forms.TermsOfUseCreateForm + success_message = ( + "La nouvelle charte d'utilisation est désormais en ligne." + ) diff --git a/benevalibre/settings/base.py b/benevalibre/settings/base.py index 6178a37..3d0ee6b 100644 --- a/benevalibre/settings/base.py +++ b/benevalibre/settings/base.py @@ -222,6 +222,7 @@ TEMPLATES = [ 'django.contrib.messages.context_processors.messages', 'benevalibre.base.context_processors.project', 'benevalibre.base.context_processors.instance_name', + 'benevalibre.instance.context_processors.terms', ], }, } diff --git a/benevalibre/templates/menus/main.html b/benevalibre/templates/menus/main.html index 046f95c..ee322a3 100644 --- a/benevalibre/templates/menus/main.html +++ b/benevalibre/templates/menus/main.html @@ -15,6 +15,13 @@ Liste des associations + {% if terms.file %} +