codestyle: add black, isort, pyupgrade (#61513)

This commit is contained in:
Paul Marillonnet 2022-02-07 18:21:05 +01:00
parent 4925ee54ae
commit 6006c96557
25 changed files with 769 additions and 611 deletions

View File

@ -1,13 +1,13 @@
#!/usr/bin/python
import sys
import os
import subprocess
from setuptools import setup, find_packages
from setuptools.command.install_lib import install_lib as _install_lib
import sys
from distutils.cmd import Command
from distutils.command.build import build as _build
from distutils.command.sdist import sdist
from distutils.cmd import Command
from setuptools import find_packages, setup
from setuptools.command.install_lib import install_lib as _install_lib
class compile_translations(Command):
@ -23,6 +23,7 @@ class compile_translations(Command):
def run(self):
try:
from django.core.management import call_command
for path, dirs, files in os.walk('src'):
if 'locale' not in dirs:
continue
@ -39,7 +40,6 @@ class build(_build):
class eo_sdist(sdist):
def run(self):
print("creating VERSION file")
if os.path.exists('VERSION'):
@ -62,27 +62,28 @@ class install_lib(_install_lib):
def get_version():
'''Use the VERSION, if absent generates a version with git describe, if not
tag exists, take 0.0- and add the length of the commit log.
tag exists, take 0.0- and add the length of the commit log.
'''
if os.path.exists('VERSION'):
with open('VERSION', 'r') as v:
with open('VERSION') as v:
return v.read()
if os.path.exists('.git'):
p = subprocess.Popen(['git','describe','--dirty=.dirty','--match=v*'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
['git', 'describe', '--dirty=.dirty', '--match=v*'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = p.communicate()[0]
if p.returncode == 0:
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
if '-' in result: # not a tagged version
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
if '-' in result: # not a tagged version
real_number, commit_count, commit_hash = result.split('-', 2)
version = '%s.post%s+%s' % (real_number, commit_count, commit_hash)
else:
version = result
return version
else:
return '0.0.post%s' % len(
subprocess.check_output(
['git', 'rev-list', 'HEAD']).splitlines())
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
return '0.0'
@ -118,6 +119,7 @@ setup(
'build': build,
'install_lib': install_lib,
'compile_translations': compile_translations,
'sdist': eo_sdist},
'sdist': eo_sdist,
},
zip_safe=False,
)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
@ -19,9 +18,10 @@
default_app_config = 'authentic2_cut.apps.AppConfig'
class Plugin(object):
class Plugin:
def get_before_urls(self):
from . import urls
return urls.urlpatterns
def get_apps(self):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
@ -19,10 +18,10 @@
from . import models
class RemoveFranceConnect(object):
class RemoveFranceConnect:
name = 'remove-franceconnect'
title = u'Supprimer la liaison FranceConnect'
confirm = u'Êtes-vous sûr?'
title = 'Supprimer la liaison FranceConnect'
confirm = 'Êtes-vous sûr?'
permission = 'custom_user.cut_fc_user'
def do(self, view, request, user, *args, **kwargs):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
@ -18,22 +17,18 @@
import base64
import uuid
from django.core.files.base import ContentFile
from django.db.transaction import atomic
from django.conf import settings
from django.db import IntegrityError
from django.core.exceptions import ValidationError
from rest_framework.generics import ListAPIView
from rest_framework import serializers, pagination
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from django_filters.rest_framework import FilterSet
from authentic2_idp_oidc.utils import make_pairwise_reversible_sub
from authentic2 import api_views
from authentic2_idp_oidc.utils import make_pairwise_reversible_sub
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.db import IntegrityError
from django.db.transaction import atomic
from django_filters.rest_framework import FilterSet
from rest_framework import pagination, serializers, status
from rest_framework.decorators import action
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from . import models, utils
@ -49,7 +44,7 @@ class Base64ImageField(serializers.FileField):
raise ValidationError('invalid base64')
identifier = uuid.uuid4()
data = ContentFile(content, name=identifier.urn[9:] + '.jpg')
return super(Base64ImageField, self).to_internal_value(data)
return super().to_internal_value(data)
class CUTValidateAttachmentSerializer(serializers.Serializer):
@ -57,9 +52,7 @@ class CUTValidateAttachmentSerializer(serializers.Serializer):
class CUTCreateValidateSerializer(serializers.Serializer):
justificatifs = serializers.ListField(
child=CUTValidateAttachmentSerializer(),
allow_empty=False)
justificatifs = serializers.ListField(child=CUTValidateAttachmentSerializer(), allow_empty=False)
external_id = serializers.CharField()
@ -90,15 +83,23 @@ class CUTValidateSerializer(serializers.ModelSerializer):
def _get_cut_validation_accepted_mime_types():
return getattr(settings, 'CUT_VALIDATION_ACCEPTED_MIME_TYPES', [
'image/jpeg',
'application/pdf',
'image/png',
])
return getattr(
settings,
'CUT_VALIDATION_ACCEPTED_MIME_TYPES',
[
'image/jpeg',
'application/pdf',
'image/png',
],
)
@action(detail=True, methods=['get', 'post'], url_path='validate',
permission_classes=(api_views.DjangoPermission('custom_user.cut_validate_user'),))
@action(
detail=True,
methods=['get', 'post'],
url_path='validate',
permission_classes=(api_views.DjangoPermission('custom_user.cut_validate_user'),),
)
def validate_cut(self, request, uuid):
user = self.get_object()
origin = get_origin(request)
@ -106,12 +107,14 @@ def validate_cut(self, request, uuid):
if request.method == 'GET':
qs = models.ValidationRequest.objects.for_origin(origin).filter(user=user).select_related('user')
serializer = CUTValidateSerializer(qs, many=True, context={'request': request})
return Response({
'result': 1,
'next': None,
'previous': None,
'results': serializer.data,
})
return Response(
{
'result': 1,
'next': None,
'previous': None,
'results': serializer.data,
}
)
serializer = CUTCreateValidateSerializer(data=request.data)
max_size = getattr(settings, 'CUT_VALIDATION_ATTACHMENT_MAX_SIZE', 300 * 1024 * 1024)
if serializer.is_valid():
@ -121,48 +124,57 @@ def validate_cut(self, request, uuid):
for i, attachment in enumerate(attachments):
content = attachment['b64_content']
if content.size > max_size:
errors.append({
'code': 'justificatifs-too-big',
'page': i,
'max-size': max_size,
})
errors.append(
{
'code': 'justificatifs-too-big',
'page': i,
'max-size': max_size,
}
)
accepted_mime_types = _get_cut_validation_accepted_mime_types()
mime_type = utils.mime_type_from_buffer(content.read(10000))
if mime_type not in accepted_mime_types:
errors.append({
'code': 'justificatifs-bad-format',
'page': i,
'accepted': accepted_mime_types,
})
errors.append(
{
'code': 'justificatifs-bad-format',
'page': i,
'accepted': accepted_mime_types,
}
)
content.name = content.name.rsplit('.', 1)[0] + '.' + mime_type.rsplit('/', 1)[1]
# rewind cursor
content.seek(0)
if models.ValidationRequest.objects.filter(
user=user, external_id=external_id).exists():
errors.append({
'code': 'already-exists',
})
if models.ValidationRequest.objects.filter(user=user, external_id=external_id).exists():
errors.append(
{
'code': 'already-exists',
}
)
if errors:
response = {'result': 0, 'errors': errors}
return Response(response, status.HTTP_400_BAD_REQUEST)
with atomic():
try:
validation_request = models.ValidationRequest.objects.create(
user=user, external_id=external_id, origin=origin)
user=user, external_id=external_id, origin=origin
)
except IntegrityError:
response = {'result': 0, 'errors': [{'code': 'already-exists'}]}
return Response(response, status.HTTP_400_BAD_REQUEST)
for attachment in attachments:
models.ValidationRequestAttachment.objects.create(
validation_request=validation_request,
image=attachment['b64_content'])
return Response({
'result': 1,
'status': 'received',
'sub': uuid,
'id': validation_request.id,
'external_id': external_id,
}, status=status.HTTP_201_CREATED)
validation_request=validation_request, image=attachment['b64_content']
)
return Response(
{
'result': 1,
'status': 'received',
'sub': uuid,
'id': validation_request.id,
'external_id': external_id,
},
status=status.HTTP_201_CREATED,
)
else:
errors = []
for key in serializer.errors:
@ -170,6 +182,7 @@ def validate_cut(self, request, uuid):
response = {'result': 0, 'errors': errors}
return Response(response, status.HTTP_400_BAD_REQUEST)
# attach new API to users' api
api_views.UsersAPI.validate_cut = validate_cut

View File

@ -15,8 +15,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class AppSettings(object):
class AppSettings:
'''Thanks django-allauth'''
__SENTINEL = object()
def __init__(self, prefix):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
@ -20,14 +19,11 @@ import copy
import logging
import django.apps
from django.conf import settings
from django.db import router, DEFAULT_DB_ALIAS
from django.utils.timezone import now, utc
from django.urls import reverse_lazy
from authentic2.constants import AUTHENTICATION_EVENTS_SESSION_KEY
from django.conf import settings
from django.db import DEFAULT_DB_ALIAS, router
from django.urls import reverse_lazy
from django.utils.timezone import now, utc
logger = logging.getLogger(__name__)
@ -43,23 +39,21 @@ class AppConfig(django.apps.AppConfig):
if not router.allow_migrate(using, Operation):
return
FC_MANAGE_OP = Operation.register(name=u'Gérer FranceConnect', slug='fc_manage')
FC_MANAGE_OP = Operation.register(name='Gérer FranceConnect', slug='fc_manage')
get_operation(FC_MANAGE_OP)
def ready(self):
from django.db.models.signals import post_migrate, post_save
from django_rbac.utils import get_ou_model
from . import api_views
post_migrate.connect(
self.post_migrate,
sender=self)
post_save.connect(
self.ou_post_save,
sender=get_ou_model())
post_migrate.connect(self.post_migrate, sender=self)
post_save.connect(self.ou_post_save, sender=get_ou_model())
def ou_post_save(self, sender, instance, raw, created, **kwargs):
from .utils import update_roles
update_roles()
def a2_hook_manager_user_data(self, view, user):
@ -73,14 +67,12 @@ class AppConfig(django.apps.AppConfig):
FranceConnectUserData(user, view.request),
)
if user.attributes.validated:
user_datas.append(
ValidationUserData(user)
)
user_datas.append(ValidationUserData(user))
return user_datas
def a2_hook_manager_modify_form(self, view, form):
from django.forms.widgets import DateTimeInput, HiddenInput, Textarea
from authentic2.passwords import generate_password
from django.forms.widgets import DateTimeInput, HiddenInput, Textarea
from . import models
@ -95,8 +87,14 @@ class AppConfig(django.apps.AppConfig):
form.fields['password2'].widget = HiddenInput()
if ou.slug == 'usagers':
del form.fields['username']
for field_name in ['is_superuser', 'validated',
'validation_context', 'phone', 'address', 'validation_date']:
for field_name in [
'is_superuser',
'validated',
'validation_context',
'phone',
'address',
'validation_date',
]:
if field_name in form.fields:
del form.fields[field_name]
@ -111,19 +109,22 @@ class AppConfig(django.apps.AppConfig):
form.fields['creation_mode'].initial = 'backoffice'
form.fields['creation_mode'].widget = HiddenInput()
form.fields['creation_partner'].initial = (
view.request.user.ou.name if view.request.user.ou else 'super-utilisateur')
view.request.user.ou.name if view.request.user.ou else 'super-utilisateur'
)
form.fields['creation_partner'].widget = HiddenInput()
form.fields['comment'].widget = Textarea(attrs={'rows': 4})
del form.fields['creation_domain']
del form.fields['validation_partner']
else:
for field_name in list(form.fields):
if field_name not in ['username',
'first_name',
'last_name',
'email',
'send_password_reset',
'generate_password']:
if field_name not in [
'username',
'first_name',
'last_name',
'email',
'send_password_reset',
'generate_password',
]:
del form.fields[field_name]
form.fields['email'].required = True
form.fields['generate_password'].initial = False
@ -139,20 +140,29 @@ class AppConfig(django.apps.AppConfig):
del form.fields['validation_partner']
if form.instance.ou and form.instance.ou.slug == 'usagers':
del form.fields['username']
for field_name in ['password1', 'password2', 'is_superuser', 'validated',
'validation_context', 'validation_date', 'ou']:
for field_name in [
'password1',
'password2',
'is_superuser',
'validated',
'validation_context',
'validation_date',
'ou',
]:
if field_name in form.fields:
del form.fields[field_name]
form.fields['comment'].widget = Textarea(attrs={'rows': 4})
else:
for field_name in list(form.fields):
if field_name not in ['username',
'first_name',
'last_name',
'email',
'is_superuser',
'generate_password',
'ou']:
if field_name not in [
'username',
'first_name',
'last_name',
'email',
'is_superuser',
'generate_password',
'ou',
]:
del form.fields[field_name]
# do not allow to move an agent in the usagers OU
form.fields['ou'].queryset = form.fields['ou'].queryset.exclude(slug='usagers')
@ -163,20 +173,25 @@ class AppConfig(django.apps.AppConfig):
user = form.instance
user.roles.remove(*list(user.roles.exclude(ou=form.instance.ou)))
return response
return edit_save
form.save = generate_new_edit_save(form.save)
# Si un compte est validé, on interdit la modification des attributs coeurs
if form.instance.attributes.validated:
for field_name in list(form.fields):
if field_name in ['first_name',
'last_name',
'birthcountry_insee',
'birthplace_insee',
'birthcountry',
'birthplace',
'gender', 'title',
'birthdate']:
if field_name in [
'first_name',
'last_name',
'birthcountry_insee',
'birthplace_insee',
'birthcountry',
'birthplace',
'gender',
'title',
'birthdate',
]:
# del form.fields[field_name]
field = form.fields[field_name]
field.required = False
@ -184,7 +199,7 @@ class AppConfig(django.apps.AppConfig):
field.widget = DateTimeInput(attrs={'readonly': ''})
attrs = field.widget.attrs or {}
attrs['disabled'] = ''
attrs['title'] = u'Champ validé'
attrs['title'] = 'Champ validé'
field.widget.attrs = attrs
def new_clean(self, field_name):
@ -194,7 +209,9 @@ class AppConfig(django.apps.AppConfig):
return getattr(self.instance, field_name)
else:
return getattr(self.instance.attributes, field_name)
return clean
setattr(form, 'clean_' + field_name, new_clean(form, field_name))
if field_name in ['generate_password']:
del form.fields[field_name]
@ -202,9 +219,16 @@ class AppConfig(django.apps.AppConfig):
if view.__class__.__name__.endswith('UserDetailView'):
if form.instance.ou:
if form.instance.ou.slug == 'usagers':
for field_name in ['username', 'is_superuser', 'validated',
'validation_context', 'validation_date', 'creation_mode',
'creation_partner', 'creation_domain']:
for field_name in [
'username',
'is_superuser',
'validated',
'validation_context',
'validation_date',
'creation_mode',
'creation_partner',
'creation_domain',
]:
if field_name in form.fields:
del form.fields[field_name]
form.fields['comment'].widget = Textarea(attrs={'readonly': '', 'rows': 4})
@ -213,8 +237,11 @@ class AppConfig(django.apps.AppConfig):
if field_name not in ['username', 'first_name', 'last_name', 'email']:
del form.fields[field_name]
if view.__class__.__name__ in ['OrganizationalUnitDetailView', 'OrganizationalUnitEditView',
'OrganizationalUnitAddView']:
if view.__class__.__name__ in [
'OrganizationalUnitDetailView',
'OrganizationalUnitEditView',
'OrganizationalUnitAddView',
]:
del form.fields['default']
del form.fields['email_is_unique']
del form.fields['username_is_unique']
@ -236,10 +263,12 @@ class AppConfig(django.apps.AppConfig):
sequence.remove('email')
sequence.insert(2, 'email')
base_columns['preferred_username'] = tables.Column(
accessor='attributes.preferred_username', verbose_name=u'Nom d\'usage')
accessor='attributes.preferred_username', verbose_name='Nom d\'usage'
)
sequence.insert(2, 'preferred_username')
base_columns['validated'] = tables.BooleanColumn(
accessor='attributes.validated', verbose_name=u'Validé')
accessor='attributes.validated', verbose_name='Validé'
)
sequence += ['validated']
else:
del base_columns['link']
@ -276,20 +305,21 @@ class AppConfig(django.apps.AppConfig):
except AttributeError:
return None
return {'Monsieur': 'male', 'Madame': 'female'}.get(title)
serializer.get_gender = get_gender
serializer.fields['gender'] = serializers.SerializerMethodField()
serializer.fields['given_name'] = serializers.CharField(read_only=True,
source='first_name')
serializer.fields['family_name'] = serializers.CharField(read_only=True,
source='last_name')
serializer.fields['given_name'] = serializers.CharField(read_only=True, source='first_name')
serializer.fields['family_name'] = serializers.CharField(read_only=True, source='last_name')
serializer.fields['email_verified'].read_only = True
serializer.fields['validation_context'] = serializers.ChoiceField(
source='attributes.validation_context',
choices=[
('online', 'online'),
('office', 'office'),
], required=False)
],
required=False,
)
serializer.fields['validation_partner'].read_only = True
serializer.fields['creation_mode'].read_only = True
serializer.fields['creation_partner'].read_only = True
@ -298,12 +328,14 @@ class AppConfig(django.apps.AppConfig):
def get_address_fc(obj):
if obj.fc_accounts.all():
return obj.fc_accounts.all()[0].get_user_info().get('address')
serializer.get_address_fc = get_address_fc
serializer.fields['address_fc'] = serializers.SerializerMethodField()
def get_phone_number_fc(obj):
if obj.fc_accounts.all():
return obj.fc_accounts.all()[0].get_user_info().get('phone_number')
serializer.get_phone_number_fc = get_phone_number_fc
serializer.fields['phone_number_fc'] = serializers.SerializerMethodField()
@ -322,6 +354,7 @@ class AppConfig(django.apps.AppConfig):
instance.attributes.creation_partner = 'UNKNOWN'
instance.attributes.creation_mode = 'BO'
return instance
serializer.create = new_create
old_update = serializer.update
@ -338,6 +371,7 @@ class AppConfig(django.apps.AppConfig):
raise NotImplementedError
instance.attributes.validation_partner = partner
return instance
serializer.update = new_update
validation_variables = ['validated', 'validation_context', 'validation_date']
@ -351,13 +385,14 @@ class AppConfig(django.apps.AppConfig):
if set(attributes) & set(validation_variables):
if instance.attributes.validated:
raise serializers.ValidationError(
'account already validated you cannot modify the validation status')
'account already validated you cannot modify the validation status'
)
if not set(validation_variables).issubset(set(attributes)):
raise serializers.ValidationError(
'validated, validation_context and validation_date are required together')
'validated, validation_context and validation_date are required together'
)
if attributes['validated'] is not True:
raise serializers.ValidationError(
'validated can only be true')
raise serializers.ValidationError('validated can only be true')
request = serializer.context['request']
if hasattr(request.user, 'oidc_client'):
pass
@ -367,12 +402,12 @@ class AppConfig(django.apps.AppConfig):
if set(attributes) & set(validation_variables):
raise serializers.ValidationError('you cannot validate during creation')
return data
serializer.validate = new_validate
serializer.fields['modified'].timezone = utc
serializer.fields['date_joined'].timezone = utc
# execute after other modifiers
a2_hook_api_modify_serializer.order = 999
@ -380,13 +415,16 @@ class AppConfig(django.apps.AppConfig):
from .custom_settings import CORE_ATTRIBUTES, CROWN_ATTRIBUTES
if view.__class__.__name__ == 'ProfileView':
context['cut_core_filled'] = all(getattr(view.request.user.attributes, a, None) for a in
CORE_ATTRIBUTES)
context['cut_crown_filled'] = any(getattr(view.request.user.attributes, a, None) for a
in CROWN_ATTRIBUTES)
context['cut_core_filled'] = all(
getattr(view.request.user.attributes, a, None) for a in CORE_ATTRIBUTES
)
context['cut_crown_filled'] = any(
getattr(view.request.user.attributes, a, None) for a in CROWN_ATTRIBUTES
)
def a2_hook_manager_modify_other_actions(self, view, other_actions):
from authentic2.manager.views import Action
from .actions import RemoveFranceConnect
class CUTValidate(Action):
@ -402,9 +440,9 @@ class AppConfig(django.apps.AppConfig):
if user.attributes.validated and user.attributes.validation_context == 'fc':
return False
if user.attributes.validated:
self.title = u'Modifier les données coeur'
self.title = 'Modifier les données coeur'
self.user = user
return super(CUTValidate, self).display(user, request)
return super().display(user, request)
class CUTJournalActions(Action):
name = 'cut-journal-actions'
@ -431,8 +469,7 @@ class AppConfig(django.apps.AppConfig):
if view.__class__.__name__ == 'EditProfile':
if form.instance and form.instance.attributes.validated:
for field in ('first_name', 'last_name', 'birthdate', 'title',
'birthplace', 'birthcountry'):
for field in ('first_name', 'last_name', 'birthdate', 'title', 'birthplace', 'birthcountry'):
form.fields.pop(field, None)
for field in form.fields.values():
if hasattr(field, 'max_length'):
@ -450,9 +487,10 @@ class AppConfig(django.apps.AppConfig):
form.fields.pop(name)
def a2_hook_api_modify_queryset(self, view, queryset):
from django.utils.timezone import now
from datetime import date
from django.utils.timezone import now
def majority(today):
for i in range(5):
try:
@ -464,16 +502,14 @@ class AppConfig(django.apps.AppConfig):
if view.__class__.__name__ == 'UsersAPI':
if hasattr(view.request.user, 'oidc_client'):
oidc_client = view.request.user.oidc_client
hide_underaged_oidc_client = getattr(settings,
'A2_CUT_HIDE_UNDERAGED_CLIENT_IDS', [])
hide_underaged_oidc_client = getattr(settings, 'A2_CUT_HIDE_UNDERAGED_CLIENT_IDS', [])
# Cache les comptes des mineurs pour les clients OIDC listés
# dans A2_CUT_HIDE_UNDERAGED_CLIENT_IDS
if oidc_client.client_id in hide_underaged_oidc_client:
from authentic2.models import AttributeValue
before = majority(now().date()).isoformat()
excluded = AttributeValue.objects.filter(
attribute__name='birthdate',
content__gt=before)
excluded = AttributeValue.objects.filter(attribute__name='birthdate', content__gt=before)
queryset = queryset.exclude(attribute_values__in=excluded)
queryset = queryset.filter(ou__slug='usagers')
return queryset
@ -490,17 +526,16 @@ class AppConfig(django.apps.AppConfig):
user_info['given_name'] = user.first_name
user_info['family_name'] = user.last_name
user_info['title'] = user.attributes.title
user_info['gender'] = {'Monsieur': 'male', 'Madame': 'female'}.get(
user.attributes.title)
user_info['birthdate'] = (user.attributes.birthdate and
user.attributes.birthdate.isoformat())
user_info['gender'] = {'Monsieur': 'male', 'Madame': 'female'}.get(user.attributes.title)
user_info['birthdate'] = user.attributes.birthdate and user.attributes.birthdate.isoformat()
user_info['birthplace'] = user.attributes.birthplace
user_info['birthcountry'] = user.attributes.birthcountry
user_info['birthplace_insee'] = user.attributes.birthplace_insee
user_info['birthcountry_insee'] = user.attributes.birthcountry_insee
user_info['validated'] = user.attributes.validated
user_info['validation_date'] = (user.attributes.validation_date and
user.attributes.validation_date.isoformat())
user_info['validation_date'] = (
user.attributes.validation_date and user.attributes.validation_date.isoformat()
)
user_info['validation_context'] = user.attributes.validation_context
# pass user.ou.slug for agent's users
if user.ou:
@ -508,11 +543,19 @@ class AppConfig(django.apps.AppConfig):
user_info['ou'] = user.ou.slug
else:
if user.attributes.validated:
for name in ['first_name', 'last_name',
'given_name', 'family_name', 'title',
'gender', 'birthdate',
'birthplace_insee', 'birthplace',
'birthcountry_insee', 'birthcountry']:
for name in [
'first_name',
'last_name',
'given_name',
'family_name',
'title',
'gender',
'birthdate',
'birthplace_insee',
'birthplace',
'birthcountry_insee',
'birthcountry',
]:
user_info['%s_verified' % name] = True
if 'crown' in scope_set:
user_info['preferred_username'] = user.attributes.preferred_username
@ -551,17 +594,13 @@ class AppConfig(django.apps.AppConfig):
def log_action(self, actor, message):
from . import models
models.Journal.objects.create(
actor=actor,
message=message)
models.Journal.objects.create(actor=actor, message=message)
def log_modification(self, actor, subject, message, mail=True, mail_message=None):
from . import models
models.Journal.objects.create(
actor=actor,
subject=subject,
message=message)
models.Journal.objects.create(actor=actor, subject=subject, message=message)
# pour les modifications sur les usagers on envoie un mail à l'usager
if mail:
self.mail_notification(actor, subject, mail_message or message)
@ -570,16 +609,20 @@ class AppConfig(django.apps.AppConfig):
from authentic2.utils.misc import send_templated_mail
if subject.ou and subject.ou.slug == 'usagers':
send_templated_mail(subject, 'authentic2/cut-notify-usager-modification',
context={
'message': message,
'usager': subject,
'agent': actor,
})
send_templated_mail(
subject,
'authentic2/cut-notify-usager-modification',
context={
'message': message,
'usager': subject,
'agent': actor,
},
)
@property
def redis_client(self):
from django.core import cache
return cache.cache.client.get_client()
def stat(self, *args):
@ -598,17 +641,17 @@ class AppConfig(django.apps.AppConfig):
method = how.upper()
msg = self.get_authentication_message(how)
if service:
msg += u' pour %s' % service
msg += ' pour %s' % service
self.log_action(user, msg)
self.stat('login', method)
def cut_event_registration(self, user, view, form, token, service, **kwargs):
# log registration for current user
creation_mode = 'FO'
msg = u'création du compte en ligne'
msg = 'création du compte en ligne'
if 'franceconnect' in token:
creation_mode = 'FC'
msg = u'création du compte via FranceConnect'
msg = 'création du compte via FranceConnect'
user.attributes.creation_mode = creation_mode
self.log_action(user, msg)
if service:
@ -617,12 +660,13 @@ class AppConfig(django.apps.AppConfig):
def cut_event_sso_request(self, idp, service, **kwargs):
self.stat('sso-request', service.slug)
from authentic2.middleware import StoreRequestMiddleware
request = StoreRequestMiddleware.get_request()
if request:
request.session['service_slug'] = service.slug
def cut_event_sso_success(self, idp, service, user, **kwargs):
msg = u'connexion à %s' % service.name
msg = 'connexion à %s' % service.name
self.log_action(user, msg)
how = self.get_authentication_how()
method = self.get_authentication_method(how)
@ -631,41 +675,40 @@ class AppConfig(django.apps.AppConfig):
def cut_event_cut_edit_core(self, user, form, **kwargs):
if not form.has_changed():
return
self.log_action(user, u'édition du profil coeur')
self.log_action(user, 'édition du profil coeur')
def cut_event_cut_edit_crown(self, user, form, **kwargs):
if not form.has_changed():
return
self.log_action(user, u'édition du profil couronne')
self.log_action(user, 'édition du profil couronne')
def cut_event_password_reset_confirm(self, user, **kwargs):
self.log_action(user, u'ré-initialisation du mot de passe')
self.log_action(user, 'ré-initialisation du mot de passe')
def cut_event_change_email_confirm(self, user, **kwargs):
self.log_action(user, u'changement de l\'adresse de courriel')
self.log_action(user, 'changement de l\'adresse de courriel')
def cut_event_delete_account(self, user, **kwargs):
self.log_action(user, u'demande de suppression du compte')
self.log_action(user, 'demande de suppression du compte')
def cut_event_change_password(self, user, **kwargs):
self.log_action(user, u'changement du mot de passe')
self.log_action(user, 'changement du mot de passe')
def cut_event_manager_action(self, user, action, instance, **kwargs):
msgs = {
'activate': u'ré-activation du compte',
'deactivate': u'suspension du compte',
'password_reset': u'envoi d\'un courriel de ré-initialisation du mot de passe',
'force_password_change': u'force un changement de mot de passe à '
u'la prochaine connexion',
'delete_password_reset': u'supprime l\'obligation de changement de mot de passe à '
u'la prochaine connexion',
'remove-franceconnect': u'suppression de la liaison FranceConnect',
'activate': 'ré-activation du compte',
'deactivate': 'suspension du compte',
'password_reset': 'envoi d\'un courriel de ré-initialisation du mot de passe',
'force_password_change': 'force un changement de mot de passe à ' 'la prochaine connexion',
'delete_password_reset': 'supprime l\'obligation de changement de mot de passe à '
'la prochaine connexion',
'remove-franceconnect': 'suppression de la liaison FranceConnect',
}
if action.name in msgs:
self.log_modification(user, instance, msgs[action.name])
def cut_event_manager_add_user(self, user, instance, **kwargs):
self.log_modification(user, instance, u'création d\'un utilisateur')
self.log_modification(user, instance, 'création d\'un utilisateur')
def cut_event_manager_edit_user(self, user, instance, form, **kwargs):
if not form.has_changed():
@ -675,47 +718,46 @@ class AppConfig(django.apps.AppConfig):
# vérifie la présence d'un attribut couronne dans le formulaire
if 'preferred_username' in form.fields:
if instance.attributes.validated:
self.log_modification(user, instance, u'modification du profil couronne')
self.log_modification(user, instance, 'modification du profil couronne')
else:
self.log_modification(user, instance,
u'modification du profil coeur et couronne')
self.log_modification(user, instance, 'modification du profil coeur et couronne')
# si on est dans le formulaire coeur on ne log rien
else:
self.log_modification(user, instance, u'modification du profil')
self.log_modification(user, instance, 'modification du profil')
def cut_event_manager_delete_user(self, user, instance, **kwargs):
self.log_action(user, u'suppression de l\'utilisateur %s' % instance)
self.mail_notification(user, instance, u'suppression de votre compte')
self.log_action(user, 'suppression de l\'utilisateur %s' % instance)
self.mail_notification(user, instance, 'suppression de votre compte')
def cut_event_manager_add_role_member(self, user, role, member, **kwargs):
self.log_modification(user, member, u'ajoute le rôle %s' % role)
self.log_modification(user, member, 'ajoute le rôle %s' % role)
def cut_event_manager_remove_role_member(self, user, role, member, **kwargs):
self.log_modification(user, member, u'retire le rôle %s' % role)
self.log_modification(user, member, 'retire le rôle %s' % role)
def cut_event_manager_cut_validate(self, user, instance, context, partner, **kwargs):
if instance.attributes.validated:
msg = u'modification du profil cœur'
msg = 'modification du profil cœur'
else:
msg = u'validation du compte'
msg = 'validation du compte'
mail_msg = msg
msg += u', contexte "%s" venant de %s' % (context, partner)
msg += ', contexte "%s" venant de %s' % (context, partner)
self.log_modification(user, instance, msg, mail_message=mail_msg)
def cut_event_manager_view_user(self, user, instance, **kwargs):
self.log_modification(user, instance, u'fiche consultée', mail=False)
self.log_modification(user, instance, 'fiche consultée', mail=False)
def cut_event_manager_change_password(self, user, instance, form, **kwargs):
if form.cleaned_data.get('generate_password'):
msg = u'génération d\'un nouveau mot de passe'
msg = 'génération d\'un nouveau mot de passe'
else:
msg = u'changement du mot de passe'
msg = 'changement du mot de passe'
if form.cleaned_data.get('send_mail'):
msg += u' envoyé par mail'
msg += ' envoyé par mail'
self.log_modification(user, instance, msg)
def cut_event_fc_link(self, user, request, **kwargs):
self.log_action(user, u'liaison avec FranceConnect')
self.log_action(user, 'liaison avec FranceConnect')
user.attributes.validated = True
user.attributes.validation_context = 'FC'
user.attributes.validation_date = now().date()
@ -726,10 +768,11 @@ class AppConfig(django.apps.AppConfig):
user.attributes.validation_partner = partner
def cut_event_fc_unlink(self, user, **kwargs):
self.log_action(user, u'déliaison de FranceConnect')
self.log_action(user, 'déliaison de FranceConnect')
def get_authentication_how(self):
from authentic2.middleware import StoreRequestMiddleware
request = StoreRequestMiddleware.get_request()
if request:
for event in request.session.get(AUTHENTICATION_EVENTS_SESSION_KEY, []):
@ -752,15 +795,15 @@ class AppConfig(django.apps.AppConfig):
def get_authentication_message(self, how):
if how.startswith('password'):
return u'connexion par mot de passe'
return 'connexion par mot de passe'
elif how == 'france-connect':
return u'connexion par FranceConnect'
return 'connexion par FranceConnect'
elif how == 'email':
return u'connexion à l\'enregistrement ou par récupération de mot de passe'
return 'connexion à l\'enregistrement ou par récupération de mot de passe'
elif how == 'oidc':
return u'connexion'
return 'connexion'
else:
return u'connexion how:%s' % how
return 'connexion how:%s' % how
def a2_hook_manager_homepage_entries(self, view):
return {

View File

@ -1,19 +1,12 @@
# -*- coding: utf-8 -*-
from authentic2.settings import INSTALLED_APPS, CACHES
import os
from authentic2.settings import CACHES, INSTALLED_APPS
PLATFORM = vars().get('PLATFORM')
if PLATFORM not in ['dev', 'test', 'prod']:
PLATFORM = 'prod'
CORE_ATTRIBUTES = [
'title',
'first_name',
'last_name',
'birthdate',
'birthplace',
'birthcountry'
]
CORE_ATTRIBUTES = ['title', 'first_name', 'last_name', 'birthdate', 'birthplace', 'birthcountry']
CROWN_ATTRIBUTES = [
'preferred_username',
@ -28,7 +21,7 @@ CROWN_ATTRIBUTES = [
'home_phone',
'professional_mobile_phone',
'professional_phone',
'birthdepartment'
'birthdepartment',
]
# Manager
@ -46,12 +39,12 @@ A2_RBAC_MANAGED_CONTENT_TYPES = ()
A2_CUT_PARTNERS = [
{
'domains': ['.lyon.fr'],
'name': u'Ville de Lyon',
'name': 'Ville de Lyon',
},
{
'domains': ['.entrouvert.org'],
'name': u'Ville de Lyon',
}
'name': 'Ville de Lyon',
},
]
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
@ -81,7 +74,7 @@ A2_FC_USER_INFO_MAPPINGS = {
'translation_simple': {
'male': 'Monsieur',
'female': 'Madame',
}
},
},
'preferred_username': {
'ref': 'preferred_username',
@ -107,28 +100,34 @@ TEMPLATE_VARS = {
"help_url": 'https://support.grandlyon.com/glc/',
"account_label": "compte GRANDLYON CONNECT",
"partners_url": "https://www.grandlyon.com/projets/metropole-intelligente.html",
"support_url": "https://support.grandlyon.com/glc/#generalites"
"support_url": "https://support.grandlyon.com/glc/#generalites",
}
if PLATFORM == 'dev':
TEMPLATE_VARS.update({
"environment_label": u"Site de développement",
"moncompte_url": 'https://connexion-grandlyon.dev.entrouvert.org',
"help_url": "https://portail-citoyen-sau.guichet-recette.grandlyon.com/glc/",
})
TEMPLATE_VARS.update(
{
"environment_label": "Site de développement",
"moncompte_url": 'https://connexion-grandlyon.dev.entrouvert.org',
"help_url": "https://portail-citoyen-sau.guichet-recette.grandlyon.com/glc/",
}
)
elif PLATFORM == 'test':
TEMPLATE_VARS.update({
"environment_label": u"Site de test",
"smarttag_js": "https://tag.aticdn.net/586793/smarttag.js",
"visits_tracking_js": "var tag = new ATInternet.Tracker.Tag();",
"moncompte_url": 'https://moncompte-rec.grandlyon.com',
"help_url": "https://portail-citoyen-sau.guichet-recette.grandlyon.com/glc/",
})
TEMPLATE_VARS.update(
{
"environment_label": "Site de test",
"smarttag_js": "https://tag.aticdn.net/586793/smarttag.js",
"visits_tracking_js": "var tag = new ATInternet.Tracker.Tag();",
"moncompte_url": 'https://moncompte-rec.grandlyon.com',
"help_url": "https://portail-citoyen-sau.guichet-recette.grandlyon.com/glc/",
}
)
elif PLATFORM == 'prod':
TEMPLATE_VARS.update({
"smarttag_js": "https://tag.aticdn.net/586794/smarttag.js",
"visits_tracking_js": "var tag = new ATInternet.Tracker.Tag();",
"moncompte_url": 'https://moncompte.grandlyon.com',
})
TEMPLATE_VARS.update(
{
"smarttag_js": "https://tag.aticdn.net/586794/smarttag.js",
"visits_tracking_js": "var tag = new ATInternet.Tracker.Tag();",
"moncompte_url": 'https://moncompte.grandlyon.com',
}
)
A2_HOMEPAGE_URL = '/accounts/'
A2_MANAGER_SITE_TITLE = 'GRANDLYON CONNECT'

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
@ -19,8 +18,10 @@ from django import forms
from . import models
class ValidationForm(forms.Form):
reason = forms.ChoiceField(
choices=(('', 'Aucune'),) + models.ValidationRequest.REASON_CHOICES,
required=False,
label=u'Raison du refus')
label='Raison du refus',
)

View File

@ -1,12 +1,12 @@
import os
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from authentic2_idp_oidc.models import OIDCClient
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from django.core.urlresolvers import reverse
from django.db.transaction import atomic
from authentic2_idp_oidc.models import OIDCClient
from authentic2_cut.models import ValidationRequest, ValidationRequestAttachment
@ -25,15 +25,11 @@ class Command(BaseCommand):
user = User.objects.get(pk=user_pk)
with atomic():
validation_request = ValidationRequest.objects.create(
user=user,
origin=oidc_client)
validation_request = ValidationRequest.objects.create(user=user, origin=oidc_client)
for path in paths:
with open(path) as file_object:
filename = os.path.basename(path)
f = ContentFile(file_object.read(), name=filename)
ValidationRequestAttachment.objects.create(
validation_request=validation_request,
image=f)
ValidationRequestAttachment.objects.create(validation_request=validation_request, image=f)
self.stdout.write('New validation request %s' % validation_request.pk)

View File

@ -1,31 +1,31 @@
from authentic2.utils.misc import redirect, same_domain
from django.conf import settings
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.deprecation import MiddlewareMixin
from authentic2.utils.misc import same_domain, redirect
from django.utils.six.moves.urllib import parse as urlparse
class CUTMiddleware(MiddlewareMixin):
'''Extract requesting domain from URL and set value on request and in session.
Domains extracted from ?next=<url> of ?redirect_uri=<url> parameters are matched against the
domains list of domain patterns.
Domains extracted from ?next=<url> of ?redirect_uri=<url> parameters are matched against the
domains list of domain patterns.
Configuration:
Configuration:
A2_CUT_PARTNERS = [
{
'domains': ['.lyon.fr'],
'logo_url': 'https://www.lyon.fr/static/logo_cut.png',
'color': '#452334',
'name': u'Ville de Lyon',
}
}
A2_CUT_PARTNERS = [
{
'domains': ['.lyon.fr'],
'logo_url': 'https://www.lyon.fr/static/logo_cut.png',
'color': '#452334',
'name': u'Ville de Lyon',
}
}
Use in templates:
Use in templates:
{% if request.partner %}{{ request.partner.name }}{% endif %}
{% if request.partner %}{{ request.partner.name }}{% endif %}
'''
MATCHES = {}
def process_request(self, request):

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -15,11 +12,33 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Journal',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='Horodatage', db_index=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
(
'timestamp',
models.DateTimeField(auto_now_add=True, verbose_name='Horodatage', db_index=True),
),
('message', models.TextField(verbose_name='Message')),
('actor', models.ForeignKey(related_name='actor_journal', verbose_name='Auteur', to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL)),
('subject', models.ForeignKey(related_name='subject_journal', verbose_name='Sujet', to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL)),
(
'actor',
models.ForeignKey(
related_name='actor_journal',
verbose_name='Auteur',
to=settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
),
),
(
'subject',
models.ForeignKey(
related_name='subject_journal',
verbose_name='Sujet',
to=settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
),
),
],
options={
'ordering': ('-timestamp', '-id'),

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -15,6 +12,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='journal',
name='subject',
field=models.ForeignKey(related_name='subject_journal', verbose_name='Sujet', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL),
field=models.ForeignKey(
related_name='subject_journal',
verbose_name='Sujet',
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.SET_NULL,
),
),
]

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -17,18 +14,80 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ValidationRequest',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Date de cr\xe9ation', db_index=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
(
'created',
models.DateTimeField(
auto_now_add=True, verbose_name='Date de cr\xe9ation', db_index=True
),
),
('origin_id', models.PositiveIntegerField(verbose_name='origin id')),
('status', models.CharField(default='received', max_length=16, verbose_name='Statut', choices=[('received', 're\xe7ue'), ('accepted', 'accept\xe9e'), ('refused', 'refus\xe9e')])),
('reason', models.TextField(blank=True, verbose_name='Raison du refus', choices=[('unreadable', 'pi\xe8ce(s) illisible(s)'), ('invalid', 'pi\xe8ce(s) invalides(s)'), ('underaged', 'invividu mineur')])),
(
'status',
models.CharField(
default='received',
max_length=16,
verbose_name='Statut',
choices=[
('received', 're\xe7ue'),
('accepted', 'accept\xe9e'),
('refused', 'refus\xe9e'),
],
),
),
(
'reason',
models.TextField(
blank=True,
verbose_name='Raison du refus',
choices=[
('unreadable', 'pi\xe8ce(s) illisible(s)'),
('invalid', 'pi\xe8ce(s) invalides(s)'),
('underaged', 'invividu mineur'),
],
),
),
('validated', models.DateTimeField(null=True, verbose_name='Date de validation')),
('external_id', models.TextField(null=True, verbose_name='Identifiant externe', blank=True)),
('taken', models.DateTimeField(null=True, verbose_name='En cours')),
('origin_ct', models.ForeignKey(verbose_name='origin ct', to='contenttypes.ContentType', on_delete=models.CASCADE)),
('taken_by', models.ForeignKey(related_name='validation_requests_taken', verbose_name='Trait\xe9 par', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)),
('user', models.ForeignKey(related_name='validation_requests', verbose_name='Utilisateur', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('validated_by', models.ForeignKey(related_name='validation_requests_validated', verbose_name='Valid\xe9/refus\xe9 par', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)),
(
'origin_ct',
models.ForeignKey(
verbose_name='origin ct', to='contenttypes.ContentType', on_delete=models.CASCADE
),
),
(
'taken_by',
models.ForeignKey(
related_name='validation_requests_taken',
verbose_name='Trait\xe9 par',
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.SET_NULL,
),
),
(
'user',
models.ForeignKey(
related_name='validation_requests',
verbose_name='Utilisateur',
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
(
'validated_by',
models.ForeignKey(
related_name='validation_requests_validated',
verbose_name='Valid\xe9/refus\xe9 par',
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.SET_NULL,
),
),
],
options={
'ordering': ('-created',),
@ -39,9 +98,20 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ValidationRequestAttachment',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('image', models.ImageField(upload_to='', verbose_name='contenu')),
('validation_request', models.ForeignKey(related_name='attachments', verbose_name='requ\xeate de validation', to='authentic2_cut.ValidationRequest', on_delete=models.CASCADE)),
(
'validation_request',
models.ForeignKey(
related_name='attachments',
verbose_name='requ\xeate de validation',
to='authentic2_cut.ValidationRequest',
on_delete=models.CASCADE,
),
),
],
options={
'ordering': ('pk',),
@ -51,6 +121,6 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='validationrequest',
unique_together=set([('origin_ct', 'origin_id', 'user', 'external_id')]),
unique_together={('origin_ct', 'origin_id', 'user', 'external_id')},
),
]

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -15,7 +12,15 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='validationrequest',
name='reason',
field=models.TextField(blank=True, verbose_name='Raison du refus', choices=[('unreadable', 'Pi\xe8ce(s) illisible(s)'), ('invalid', 'Pi\xe8ce(s) invalides(s)'), ('underaged', 'Invividu mineur')]),
field=models.TextField(
blank=True,
verbose_name='Raison du refus',
choices=[
('unreadable', 'Pi\xe8ce(s) illisible(s)'),
('invalid', 'Pi\xe8ce(s) invalides(s)'),
('underaged', 'Invividu mineur'),
],
),
),
migrations.AlterField(
model_name='validationrequest',
@ -25,6 +30,13 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='validationrequest',
name='validated_by',
field=models.ForeignKey(related_name='validation_requests_validated', verbose_name='Valid\xe9/refus\xe9 par', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL),
field=models.ForeignKey(
related_name='validation_requests_validated',
verbose_name='Valid\xe9/refus\xe9 par',
blank=True,
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.SET_NULL,
),
),
]

View File

@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-15 14:16
from __future__ import unicode_literals
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -17,6 +15,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='journal',
name='actor',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='actor_journal', to=settings.AUTH_USER_MODEL, verbose_name='Auteur'),
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='actor_journal',
to=settings.AUTH_USER_MODEL,
verbose_name='Auteur',
),
),
]

View File

@ -1,42 +1,36 @@
# -*- coding: utf-8 -*-
import os.path
from datetime import timedelta
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.conf import settings
from django.db.models.query import Q
from django.utils.timezone import now
from django.urls import reverse
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.files.storage import default_storage
from django.db import models
from django.db.models.query import Q
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.contrib.contenttypes.fields import GenericForeignKey
from sorl.thumbnail import get_thumbnail, delete
from django.urls import reverse
from django.utils.timezone import now
from sorl.thumbnail import delete, get_thumbnail
class Journal(models.Model):
timestamp = models.DateTimeField(
verbose_name=u'Horodatage',
db_index=True,
auto_now_add=True)
timestamp = models.DateTimeField(verbose_name='Horodatage', db_index=True, auto_now_add=True)
actor = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
verbose_name=u'Auteur',
verbose_name='Auteur',
related_name='actor_journal',
on_delete=models.SET_NULL)
on_delete=models.SET_NULL,
)
subject = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name='Sujet',
null=True,
related_name='subject_journal',
on_delete=models.SET_NULL)
message = models.TextField(
verbose_name='Message')
on_delete=models.SET_NULL,
)
message = models.TextField(verbose_name='Message')
class Meta:
verbose_name = 'historique'
@ -60,9 +54,10 @@ class ValidationRequestManager(models.Manager):
# la demande est prise par <user>
qs = qs.filter(
Q(status=ValidationRequest.STATUS_RECEIVED)
& (Q(taken__isnull=True)
| Q(taken__lt=now() - timedelta(seconds=self.model.TAKEN_DELAY))
| Q(taken_by=user)
& (
Q(taken__isnull=True)
| Q(taken__lt=now() - timedelta(seconds=self.model.TAKEN_DELAY))
| Q(taken_by=user)
)
)
# on veut la prochaine demande à traiter
@ -91,69 +86,54 @@ class ValidationRequest(models.Model):
STATUS_REFUSED = 'refused'
STATUS_CHOICES = (
(STATUS_RECEIVED, u'reçue'),
(STATUS_ACCEPTED, u'acceptée'),
(STATUS_REFUSED, u'refusée'),
(STATUS_RECEIVED, 'reçue'),
(STATUS_ACCEPTED, 'acceptée'),
(STATUS_REFUSED, 'refusée'),
)
REASON_UNREADABLE = 'unreadable'
REASON_INVALID = 'invalid'
REASON_UNDERAGED = 'underaged'
REASON_CHOICES = (
(REASON_UNREADABLE, u'Pièce(s) illisible(s)'),
(REASON_INVALID, u'Pièce(s) invalides(s)'),
(REASON_UNDERAGED, u'Invividu mineur'),
(REASON_UNREADABLE, 'Pièce(s) illisible(s)'),
(REASON_INVALID, 'Pièce(s) invalides(s)'),
(REASON_UNDERAGED, 'Invividu mineur'),
)
created = models.DateTimeField(
verbose_name=u'Date de création',
db_index=True,
auto_now_add=True)
created = models.DateTimeField(verbose_name='Date de création', db_index=True, auto_now_add=True)
origin_ct = models.ForeignKey(
'contenttypes.ContentType',
verbose_name='origin ct',
on_delete=models.CASCADE)
origin_id = models.PositiveIntegerField(
verbose_name='origin id')
'contenttypes.ContentType', verbose_name='origin ct', on_delete=models.CASCADE
)
origin_id = models.PositiveIntegerField(verbose_name='origin id')
origin = GenericForeignKey('origin_ct', 'origin_id')
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=u'Utilisateur',
verbose_name='Utilisateur',
related_name='validation_requests',
on_delete=models.CASCADE)
on_delete=models.CASCADE,
)
status = models.CharField(
max_length=16,
choices=STATUS_CHOICES,
verbose_name=u'Statut',
default=STATUS_RECEIVED)
reason = models.TextField(
choices=REASON_CHOICES,
blank=True,
verbose_name=u'Raison du refus')
validated = models.DateTimeField(
null=True,
blank=True,
verbose_name=u'Date de validation')
max_length=16, choices=STATUS_CHOICES, verbose_name='Statut', default=STATUS_RECEIVED
)
reason = models.TextField(choices=REASON_CHOICES, blank=True, verbose_name='Raison du refus')
validated = models.DateTimeField(null=True, blank=True, verbose_name='Date de validation')
validated_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=u'Validé/refusé par',
verbose_name='Validé/refusé par',
null=True,
blank=True,
related_name='validation_requests_validated',
on_delete=models.SET_NULL)
external_id = models.TextField(
blank=True,
null=True,
verbose_name=u'Identifiant externe')
taken = models.DateTimeField(
verbose_name=u'En cours',
null=True)
on_delete=models.SET_NULL,
)
external_id = models.TextField(blank=True, null=True, verbose_name='Identifiant externe')
taken = models.DateTimeField(verbose_name='En cours', null=True)
taken_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=u'Traité par',
verbose_name='Traité par',
null=True,
related_name='validation_requests_taken',
on_delete=models.SET_NULL)
on_delete=models.SET_NULL,
)
objects = ValidationRequestManager()
@ -170,14 +150,14 @@ class ValidationRequest(models.Model):
@property
def human_status(self):
if self.status == self.STATUS_REFUSED:
return u'refusé'
return 'refusé'
elif self.status == self.STATUS_ACCEPTED:
return u'accepté'
return 'accepté'
elif self.status == self.STATUS_RECEIVED:
if self.is_taken:
return u'en traitement'
return 'en traitement'
else:
return u'reçu'
return 'reçu'
else:
raise NotImplementedError
@ -198,23 +178,21 @@ class ValidationRequest(models.Model):
self.save()
class Meta:
verbose_name = u'Requête de validation'
verbose_name_plural = u'Requêtes de validation'
verbose_name = 'Requête de validation'
verbose_name_plural = 'Requêtes de validation'
ordering = ('-created',)
unique_together = (
('origin_ct', 'origin_id', 'user', 'external_id'),
)
unique_together = (('origin_ct', 'origin_id', 'user', 'external_id'),)
class ValidationRequestAttachment(models.Model):
validation_request = models.ForeignKey(
ValidationRequest,
verbose_name=u'requête de validation',
verbose_name='requête de validation',
related_name='attachments',
on_delete=models.CASCADE)
on_delete=models.CASCADE,
)
image = models.ImageField(
verbose_name='contenu')
image = models.ImageField(verbose_name='contenu')
@property
def extension(self):
@ -224,10 +202,8 @@ class ValidationRequestAttachment(models.Model):
def url(self):
return reverse(
'cut-manager-user-validation-attachment',
kwargs={
'pk': self.pk,
'filename': self.image.name.rsplit('/', 1)[-1]
})
kwargs={'pk': self.pk, 'filename': self.image.name.rsplit('/', 1)[-1]},
)
@property
def thumbnail(self):
@ -241,7 +217,7 @@ class ValidationRequestAttachment(models.Model):
with default_storage.open(thumbnail.name):
pass
return thumbnail
except IOError:
except OSError:
pass
return None
@ -252,10 +228,7 @@ class ValidationRequestAttachment(models.Model):
return {
'src': reverse(
'cut-manager-user-validation-attachment-thumbnail',
kwargs={
'pk': self.pk,
'filename': self.image.name.rsplit('/', 1)[-1]
}
kwargs={'pk': self.pk, 'filename': self.image.name.rsplit('/', 1)[-1]},
),
'height': thumbnail.height,
'width': thumbnail.width,
@ -264,8 +237,8 @@ class ValidationRequestAttachment(models.Model):
class Meta:
ordering = ('pk',)
verbose_name = u'Pièce jointe'
verbose_name_plural = u'Pièces jointes'
verbose_name = 'Pièce jointe'
verbose_name_plural = 'Pièces jointes'
@receiver(post_delete, sender=ValidationRequestAttachment)
@ -273,7 +246,7 @@ def auto_delete_image_files(sender, instance, **kwargs):
if instance.image:
try:
delete(instance.image)
except IOError:
except OSError:
pass
if os.path.isfile(instance.image.path):
instance.image.delete(save=False)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
@ -15,9 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext_lazy as _
import django_tables2 as tables
from django.utils.translation import ugettext_lazy as _
from . import models
@ -40,26 +38,23 @@ class UserModificationsTable(tables.Table):
class ValidationTable(tables.Table):
pk = tables.LinkColumn(
viewname='cut-manager-user-validation',
kwargs={'pk': tables.A('pk')},
verbose_name='Identifiant')
viewname='cut-manager-user-validation', kwargs={'pk': tables.A('pk')}, verbose_name='Identifiant'
)
user = tables.LinkColumn(
viewname='a2-manager-user-detail',
kwargs={'pk': tables.A('user.pk')},
accessor='user.get_full_name',
order_by=('user__last_name', 'user__first_name'))
origin = tables.Column(
verbose_name='Service demandeur',
orderable=False)
human_status = tables.Column(
order_by=('status',),
verbose_name='Statut')
order_by=('user__last_name', 'user__first_name'),
)
origin = tables.Column(verbose_name='Service demandeur', orderable=False)
human_status = tables.Column(order_by=('status',), verbose_name='Statut')
human_status_who = tables.LinkColumn(
verbose_name='Agent',
viewname='a2-manager-user-detail',
accessor='human_status_who.get_full_name',
kwargs={'pk': tables.A('human_status_who.pk')},
orderable=False)
orderable=False,
)
class Meta:
model = models.ValidationRequest

View File

@ -14,41 +14,55 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import url
from authentic2.manager.urls import manager_login_required
from . import views, api_views
from authentic2.decorators import required
from authentic2.manager.urls import manager_login_required
from django.conf.urls import url
from . import api_views, views
urlpatterns = required(
manager_login_required, [
manager_login_required,
[
url('^accounts/edit-core/$', views.edit_core, name='cut-edit-core'),
url('^accounts/edit-crown/$', views.edit_crown, name='cut-edit-crown'),
url(r'^manage/users/(?P<pk>\d+)/$', views.manager_user_detail,
name='a2-manager-user-detail'),
url(r'^manage/users/uuid:(?P<slug>[a-z0-9]+)/$', views.manager_user_detail,
name='a2-manager-user-by-uuid-detail'),
url('^manage/users/(?P<pk>\d+)/edit-core/$', views.manager_user_edit_core,
name='cut-manager-user-edit-core'),
url('^manage/users/(?P<pk>\d+)/actions-journal/$', views.user_actions_journal,
name='cut-manager-user-actions-journal'),
url('^manage/users/(?P<pk>\d+)/modifications-journal/$', views.user_modifications_journal,
name='cut-manager-user-modifications-journal'),
url('^manage/validation/$', views.validation_homepage,
name='cut-manager-user-validation'),
url('^manage/validation/next/$', views.next_validation,
name='cut-manager-user-next-validation'),
url('^manage/validation/(?P<pk>\d+)/$', views.validation,
name='cut-manager-user-validation'),
url('^manage/validation/attachment/(?P<pk>\d*)/(?P<filename>.*)$', views.validation_attachment,
name='cut-manager-user-validation-attachment'),
url('^manage/validation/attachment-thumbnail/(?P<pk>\d*)/(?P<filename>.*)$', views.validation_attachment_thumbnail,
name='cut-manager-user-validation-attachment-thumbnail'),
]
url(r'^manage/users/(?P<pk>\d+)/$', views.manager_user_detail, name='a2-manager-user-detail'),
url(
r'^manage/users/uuid:(?P<slug>[a-z0-9]+)/$',
views.manager_user_detail,
name='a2-manager-user-by-uuid-detail',
),
url(
r'^manage/users/(?P<pk>\d+)/edit-core/$',
views.manager_user_edit_core,
name='cut-manager-user-edit-core',
),
url(
r'^manage/users/(?P<pk>\d+)/actions-journal/$',
views.user_actions_journal,
name='cut-manager-user-actions-journal',
),
url(
r'^manage/users/(?P<pk>\d+)/modifications-journal/$',
views.user_modifications_journal,
name='cut-manager-user-modifications-journal',
),
url('^manage/validation/$', views.validation_homepage, name='cut-manager-user-validation'),
url('^manage/validation/next/$', views.next_validation, name='cut-manager-user-next-validation'),
url(r'^manage/validation/(?P<pk>\d+)/$', views.validation, name='cut-manager-user-validation'),
url(
r'^manage/validation/attachment/(?P<pk>\d*)/(?P<filename>.*)$',
views.validation_attachment,
name='cut-manager-user-validation-attachment',
),
url(
r'^manage/validation/attachment-thumbnail/(?P<pk>\d*)/(?P<filename>.*)$',
views.validation_attachment_thumbnail,
name='cut-manager-user-validation-attachment-thumbnail',
),
],
)
urlpatterns += [
url('^cgu/$', views.cgu,
name='cut-cgu'),
url('^cgu/$', views.cgu, name='cut-cgu'),
url('^api/validate/$', api_views.validate, name='api-cut-validate'),
]

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2016 Entr'ouvert
#
@ -18,20 +17,21 @@
from django.utils.html import format_html
class FranceConnectUserData(object):
class FranceConnectUserData:
def __init__(self, user, request):
self.user = user
self.request = request
def __str__(self):
from authentic2_auth_fc.models import FcAccount
if FcAccount.objects.filter(user=self.user).exists():
return format_html(u'<p>Utilisateur relié à un compte FranceConnect</p>')
return format_html('<p>Utilisateur relié à un compte FranceConnect</p>')
else:
return u''
return ''
class ValidationUserData(object):
class ValidationUserData:
def __init__(self, user):
self.user = user
@ -45,8 +45,9 @@ class ValidationUserData(object):
validation_context = self.user.attributes.validation_context
validation_date = self.user.attributes.validation_date
if not validation_date:
return u''
return format_html(u'<p id="a2-manager-user-cut-validation">Compte validé <em>{0}</em> le '
u'{1}</p>',
context_map.get(validation_context, validation_context),
validation_date.strftime('%d/%m/%Y'))
return ''
return format_html(
'<p id="a2-manager-user-cut-validation">Compte validé <em>{0}</em> le ' '{1}</p>',
context_map.get(validation_context, validation_context),
validation_date.strftime('%d/%m/%Y'),
)

View File

@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
from django.contrib.contenttypes.models import ContentType
from authentic2.a2_rbac.models import ACTIVATE_OP, RESET_PASSWORD_OP
from django.contrib.auth import get_user_model
from django_rbac.utils import get_ou_model, get_role_model, get_operation, get_permission_model
from django_rbac.models import CHANGE_OP, SEARCH_OP, ADD_OP, VIEW_OP, DELETE_OP, ADMIN_OP, Operation
from authentic2.a2_rbac.models import RESET_PASSWORD_OP, ACTIVATE_OP
from django.contrib.contenttypes.models import ContentType
from django_rbac.models import ADD_OP, ADMIN_OP, CHANGE_OP, DELETE_OP, SEARCH_OP, VIEW_OP, Operation
from django_rbac.utils import get_operation, get_ou_model, get_permission_model, get_role_model
OU = get_ou_model()
Role = get_role_model()
@ -17,70 +15,70 @@ CUT_FC_OP = Operation.register(name='Modifier liaison FC', slug='cut_fc')
ROLE_TEMPLATES = [
{
'name': u'Administrateur CUT - création',
'name': 'Administrateur CUT - création',
'slug': '_a2-cut-create',
'operations': [ADD_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - recherche',
'name': 'Administrateur CUT - recherche',
'slug': '_a2-cut-search',
'operations': [SEARCH_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - lecture',
'name': 'Administrateur CUT - lecture',
'slug': '_a2-cut-view',
'operations': [VIEW_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - modification',
'name': 'Administrateur CUT - modification',
'slug': '_a2-cut-change',
'operations': [CHANGE_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - suppression',
'name': 'Administrateur CUT - suppression',
'slug': '_a2-cut-delete',
'operations': [DELETE_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - mot de passe',
'name': 'Administrateur CUT - mot de passe',
'slug': '_a2-cut-passwords',
'operations': [RESET_PASSWORD_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - suspension',
'name': 'Administrateur CUT - suspension',
'slug': '_a2-cut-activate',
'operations': [ACTIVATE_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - validation',
'name': 'Administrateur CUT - validation',
'slug': '_a2-cut-validate',
'operations': [CUT_VALIDATE_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT - fc',
'name': 'Administrateur CUT - fc',
'slug': '_a2-cut-fc',
'operations': [CUT_FC_OP, SEARCH_OP, VIEW_OP],
'target': 'user_ct',
'scope': 'ou_usagers',
},
{
'name': u'Administrateur CUT',
'name': 'Administrateur CUT',
'slug': '_a2-cut-admin-usagers',
'role_parents': [
'_a2-cut-create',
@ -92,10 +90,10 @@ ROLE_TEMPLATES = [
'_a2-cut-activate',
'_a2-cut-validate',
'_a2-cut-fc',
]
],
},
{
'name': u'Utilisateurs',
'name': 'Utilisateurs',
'slug': '_a2-cut-admin-users',
'operations': [ADMIN_OP],
'target': 'user_ct',
@ -103,7 +101,7 @@ ROLE_TEMPLATES = [
'child_of_ou': ['ou_territoire', '__prefix__'],
},
{
'name': u'Rôles',
'name': 'Rôles',
'slug': '_a2-cut-admin-roles',
'operations': [ADMIN_OP],
'target': 'role_ct',
@ -111,7 +109,7 @@ ROLE_TEMPLATES = [
'child_of_ou': ['ou_territoire', '__prefix__'],
},
{
'name': u'Administrateur lecteur',
'name': 'Administrateur lecteur',
'slug': '_a2-cut-admin-view-only',
'operations': [VIEW_OP],
'target': 'user_ct',
@ -119,12 +117,9 @@ ROLE_TEMPLATES = [
'child_of_ou': ['ou_territoire', '__prefix__'],
},
{
'name': u'Administrateur',
'name': 'Administrateur',
'slug': '_a2-cut-admin',
'role_parents': [
'_a2-cut-admin-users',
'_a2-cut-admin-roles'
],
'role_parents': ['_a2-cut-admin-users', '_a2-cut-admin-roles'],
'scope': 'no_scope',
},
]
@ -141,22 +136,18 @@ def update_roles():
default_ou.save()
ou_usagers, created = OU.objects.get_or_create(
slug='usagers',
defaults={
'name': u'Usagers',
'default': True
}
slug='usagers', defaults={'name': 'Usagers', 'default': True}
)
if ou_usagers.default is False:
ou_usagers.default = True
ou_usagers.save()
ou_territoire, created = OU.objects.get_or_create(
name=u'Territoire',
name='Territoire',
slug='territoire',
defaults={
'name': u'Territoire',
}
'name': 'Territoire',
},
)
ct_ct = ContentType.objects.get_for_model(ContentType)
@ -174,11 +165,7 @@ def update_roles():
return
for tpl in ROLE_TEMPLATES:
role, created = Role.objects.get_or_create(
slug=tpl['slug'],
ou=ou,
defaults={
'name': tpl['name']
}
slug=tpl['slug'], ou=ou, defaults={'name': tpl['name']}
)
roles[(ou, tpl['slug'])] = role
if tpl.get('operations'):
@ -191,16 +178,13 @@ def update_roles():
for operation in tpl['operations']:
op = get_operation(operation)
permission, created = Permission.objects.get_or_create(
operation=op,
ou=scope,
target_ct=ct_ct,
target_id=target_ct.pk)
operation=op, ou=scope, target_ct=ct_ct, target_id=target_ct.pk
)
permissions.append(permission)
op = get_operation(VIEW_OP)
permission, created = Permission.objects.get_or_create(
operation=op,
target_ct=ou_ct,
target_id=scope.pk)
operation=op, target_ct=ou_ct, target_id=scope.pk
)
permissions.append(permission)
role.permissions.set(permissions)
else:
@ -219,9 +203,7 @@ def update_roles():
elif ou.slug != child:
child_ou = vars()[child]
if child_ou:
child_role = Role.objects.get(
ou=child_ou,
slug=tpl['slug'])
child_role = Role.objects.get(ou=child_ou, slug=tpl['slug'])
role.add_child(child_role)
if 'role_parents' in tpl:
for role_parent in tpl['role_parents']:
@ -230,12 +212,12 @@ def update_roles():
if tpl['name'] == 'Administrateur' and ou.slug == 'territoire':
op = get_operation(ADMIN_OP)
permission, created = Permission.objects.get_or_create(
operation=op,
target_ct=ct_ct,
target_id=ou_ct.pk)
operation=op, target_ct=ct_ct, target_id=ou_ct.pk
)
role.permissions.add(permission)
slugs = [tpl['slug'] for tpl in ROLE_TEMPLATES]
Role.objects.filter(slug__startswith='_a2-cut').exclude(slug__in=slugs).delete()
handle_ou(ou_territoire, **vars())
for ou in OU.objects.exclude(slug__in=['usagers', 'territoire']):
handle_ou(**vars())

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# authentic2_cut - Authentic2 plugin for CUT
# Copyright (C) 2017 Entr'ouvert
#
@ -17,23 +16,22 @@
from contextlib import closing
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from authentic2 import hooks
from authentic2.manager.user_views import UserDetailView, UserEditView
from authentic2.manager.views import BaseTableView, FilterQuerysetByPermMixin, SimpleSubTableView
from authentic2.utils.misc import redirect
from authentic2.views import EditProfile
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.views.generic.base import TemplateView
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.db.transaction import atomic
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView
from authentic2.views import EditProfile
from authentic2.manager.views import SimpleSubTableView, BaseTableView, FilterQuerysetByPermMixin
from authentic2.manager.user_views import UserEditView, UserDetailView
from authentic2 import hooks
from authentic2.utils.misc import redirect
from . import forms, models, tables, utils
from .custom_settings import CORE_ATTRIBUTES
from . import models, forms, tables, utils
class EditCoreView(EditProfile):
@ -49,14 +47,15 @@ class EditCoreView(EditProfile):
@classmethod
def get_fields(cls, scopes=None):
fields, labels = super(EditCoreView, cls).get_fields(scopes=scopes)
fields, labels = super().get_fields(scopes=scopes)
return [field for field in fields if field in cls.fields], labels
def form_valid(self, form):
response = super(EditCoreView, self).form_valid(form)
response = super().form_valid(form)
hooks.call_hooks('event', name='cut-edit-core', user=self.request.user, form=form)
return response
edit_core = EditCoreView.as_view()
@ -73,14 +72,15 @@ class EditCrownView(EditProfile):
@classmethod
def get_fields(cls, scopes=None):
fields, labels = super(EditCrownView, cls).get_fields(scopes=scopes)
fields, labels = super().get_fields(scopes=scopes)
return [field for field in fields if field not in cls.fields], labels
def form_valid(self, form):
response = super(EditCrownView, self).form_valid(form)
response = super().form_valid(form)
hooks.call_hooks('event', name='cut-edit-crown', user=self.request.user, form=form)
return response
edit_crown = EditCrownView.as_view()
@ -90,21 +90,21 @@ class UserEditCoreView(UserEditView):
def get_title(self):
if self.object.attributes.validated:
return u'Modifier les attributs cœurs'
return u'Valider les attributs cœurs'
return 'Modifier les attributs cœurs'
return 'Valider les attributs cœurs'
def get_fields(self):
fields = super(UserEditCoreView, self).get_fields()
fields = super().get_fields()
return [field for field in CORE_ATTRIBUTES if field in fields]
def get_form(self, *args, **kwargs):
form = super(UserEditCoreView, self).get_form(*args, **kwargs)
form = super().get_form(*args, **kwargs)
for field in form.fields.values():
field.required = True
return form
def form_valid(self, form, context='office', partner=''):
response = super(UserEditCoreView, self).form_valid(form)
response = super().form_valid(form)
already_validated = form.instance.attributes.validated
if form.has_changed() or not already_validated:
form.instance.attributes.validated = True
@ -113,28 +113,42 @@ class UserEditCoreView(UserEditView):
if not partner and self.request.user.ou:
partner = self.request.user.ou.slug
form.instance.attributes.validation_partner = partner
hooks.call_hooks('event', user=self.request.user, name='manager-cut-validate',
instance=form.instance, form=form, context=context, partner=partner)
hooks.call_hooks(
'event',
user=self.request.user,
name='manager-cut-validate',
instance=form.instance,
form=form,
context=context,
partner=partner,
)
if already_validated:
msg = u'Les données cœur ont été modifiées.'
msg = 'Les données cœur ont été modifiées.'
else:
msg = u'Le compte a été validé.'
msg = 'Le compte a été validé.'
messages.info(self.request, msg)
return response
manager_user_edit_core = UserEditCoreView.as_view()
class ManagerUserDetailView(UserDetailView):
def get(self, request, *args, **kwargs):
response = super(ManagerUserDetailView, self).get(request, *args, **kwargs)
response = super().get(request, *args, **kwargs)
# journalise les accès aux fiches une fois par heure et par session
t = now()
key = 'user-looked-%s-%s-%s' % (self.object, t.date(), t.time().hour)
if key not in request.session:
request.session[key] = True
hooks.call_hooks('event', name='manager-view-user', user=request.user,
instance=self.object, request=request, **kwargs)
hooks.call_hooks(
'event',
name='manager-view-user',
user=request.user,
instance=self.object,
request=request,
**kwargs,
)
return response
@ -151,6 +165,7 @@ class UserActionsJournal(SimpleSubTableView):
def get_table_queryset(self):
return self.object.actor_journal.all()
user_actions_journal = UserActionsJournal.as_view()
@ -164,6 +179,7 @@ class UserModificationsJournal(SimpleSubTableView):
def get_table_queryset(self):
return self.object.subject_journal.all()
user_modifications_journal = UserModificationsJournal.as_view()
@ -172,7 +188,8 @@ class CGU(TemplateView):
def get_context_data(self, **kwargs):
kwargs['cut_base_url'] = self.request.build_absolute_uri('/')
return super(CGU, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
cgu = CGU.as_view()
@ -183,10 +200,9 @@ def next_validation(request):
with atomic():
validation_request = models.ValidationRequest.objects.next_request(request.user)
if not validation_request:
messages.info(request, u'Il n\'y aucune demande de validation en ce moment.')
messages.info(request, 'Il n\'y aucune demande de validation en ce moment.')
return redirect(request, 'a2-manager-homepage')
return redirect(request, 'cut-manager-user-validation',
kwargs={'pk': validation_request.pk})
return redirect(request, 'cut-manager-user-validation', kwargs={'pk': validation_request.pk})
class Validation(UserEditCoreView):
@ -195,21 +211,21 @@ class Validation(UserEditCoreView):
def get_object(self, queryset=None):
qs = models.ValidationRequest.objects.all()
self.validation_request = super(Validation, self).get_object(queryset=qs)
self.validation_request = super().get_object(queryset=qs)
return self.validation_request.user
def dispatch(self, request, *args, **kwargs):
# si la demande en cours a déjà été traitée, on passe à la suivante
try:
return super(Validation, self).dispatch(request, *args, **kwargs)
return super().dispatch(request, *args, **kwargs)
except Http404:
return redirect(request, 'cut-manager-user-next-validation')
def get_context_data(self, **kwargs):
ctx = super(Validation, self).get_context_data(**kwargs)
ctx = super().get_context_data(**kwargs)
ctx['validation_request'] = self.validation_request
ctx['attachments'] = self.validation_request.attachments.all()
ctx['action'] = u'Valider'
ctx['action'] = 'Valider'
ctx['validation_form'] = forms.ValidationForm()
return ctx
@ -221,9 +237,9 @@ class Validation(UserEditCoreView):
if 'refuse' in request.POST:
reason = request.POST.get('reason')
if not reason:
messages.error(request, u'Vous devez préciser une raison pour le refus.')
messages.error(request, 'Vous devez préciser une raison pour le refus.')
return HttpResponseRedirect('')
messages.info(request, u'Demande traitée.')
messages.info(request, 'Demande traitée.')
self.validation_request.reason = reason
self.validation_request.status = models.ValidationRequest.STATUS_REFUSED
self.validation_request.validated = now()
@ -231,7 +247,7 @@ class Validation(UserEditCoreView):
self.validation_request.save()
return self.next_request(request)
elif 'validate' in request.POST:
return super(Validation, self).post(request, *args, **kwargs)
return super().post(request, *args, **kwargs)
else: # next
return self.next_request(request)
@ -240,11 +256,11 @@ class Validation(UserEditCoreView):
# recommençons du début
with atomic():
validation_request = models.ValidationRequest.objects.next_request(
request.user, after=self.validation_request)
request.user, after=self.validation_request
)
if not validation_request:
return redirect(request, 'cut-manager-user-next-validation')
return redirect(request, 'cut-manager-user-validation',
kwargs={'pk': validation_request.pk})
return redirect(request, 'cut-manager-user-validation', kwargs={'pk': validation_request.pk})
def form_valid(self, form):
partner = ''
@ -257,12 +273,20 @@ class Validation(UserEditCoreView):
form.instance.attributes.validation_context = 'online'
form.instance.attributes.validation_date = now().date()
form.instance.attributes.validation_partner = partner
hooks.call_hooks('event', user=self.request.user, name='manager-cut-validate',
instance=form.instance, form=form, context='office', partner=partner)
hooks.call_hooks(
'event',
user=self.request.user,
name='manager-cut-validate',
instance=form.instance,
form=form,
context='office',
partner=partner,
)
self.validation_request.accept(self.request.user)
messages.info(self.request, u'Demande traitée.')
messages.info(self.request, 'Demande traitée.')
return self.next_request(self.request)
validation = Validation.as_view()
@ -300,4 +324,5 @@ class ValidationHomepage(BaseTableView):
qs = qs.prefetch_related('origin').select_related()
return qs
validation_homepage = ValidationHomepage.as_view()

View File

@ -1,17 +1,17 @@
import copy
import time
import pytest
import django_webtest
from collections import namedtuple
import django_webtest
import pytest
try:
import pathlib
except ImportError:
import pathlib2 as pathlib
from django.core.management import call_command
from django.contrib.auth import get_user_model
from django.core.management import call_command
from django_rbac.utils import get_ou_model
User = get_user_model()
@ -59,10 +59,11 @@ def glc_app(app, glc):
return app
class AllHook(object):
class AllHook:
def __init__(self):
self.calls = {}
from authentic2 import hooks
hooks.get_hooks.cache.clear()
def __call__(self, hook_name, *args, **kwargs):
@ -83,7 +84,8 @@ def user(db):
email='john.doe@example.net',
first_name='John',
last_name='Doe',
email_verified=True)
email_verified=True,
)
user.set_password('john.doe')
return user
@ -102,11 +104,7 @@ def hooks(settings):
@pytest.fixture
def admin(db):
user = User(
username='admin',
email='admin@example.net',
is_superuser=True,
is_staff=True)
user = User(username='admin', email='admin@example.net', is_superuser=True, is_staff=True)
user.ou = OU.objects.get(slug='territoire')
user.set_password('admin')
user.save()
@ -135,7 +133,7 @@ def pdf_file():
def redis_client(monkeypatch):
from authentic2_cut.apps import AppConfig
class RedisClientMock(object):
class RedisClientMock:
def __init__(self):
self.contents = {}
self.timeouts = {}
@ -158,4 +156,5 @@ def redis_client(monkeypatch):
@pytest.fixture(autouse=True)
def clean_caches():
from authentic2.apps.journal.models import event_type_cache
event_type_cache.cache.clear()

View File

@ -1,30 +1,30 @@
# -*- coding: utf-8 -*-
import base64
import uuid
import pytest
from django.contrib.auth import get_user_model
from django.utils.encoding import force_text
from utils import login
from authentic2_cut import models
from utils import login
User = get_user_model()
JOHN = u'Jôhn'
DOE = u'Dôe'
JOHN = 'Jôhn'
DOE = 'Dôe'
EMAIL = 'john.doe@example.com'
@pytest.fixture
def john(glc_app):
response = glc_app.post_json('/api/users/', params={
'first_name': JOHN,
'last_name': DOE,
'email': EMAIL,
})
response = glc_app.post_json(
'/api/users/',
params={
'first_name': JOHN,
'last_name': DOE,
'email': EMAIL,
},
)
user = User.objects.get(first_name=JOHN)
assert response.json['sub'] != user.uuid
assert response.json['first_name'] == JOHN
@ -44,7 +44,7 @@ def test_no_email(glc_app):
app = glc_app
response = app.post_json('/api/users/', params={}, status=400)
assert set(response.json['errors']) == set(['first_name', 'last_name', 'email'])
assert set(response.json['errors']) == {'first_name', 'last_name', 'email'}
assert response.json['result'] == 0
@ -54,12 +54,18 @@ def test_create_user(john):
def helper_test_validation_image(glc_app, john, image_file, extension):
external_id = uuid.uuid4().hex
response = glc_app.post_json('/api/users/%s/validate/' % john._oidc_sub, params={
'external_id': external_id,
'justificatifs': [{
'b64_content': force_text(base64.b64encode(image_file)),
}],
}, status=201)
response = glc_app.post_json(
'/api/users/%s/validate/' % john._oidc_sub,
params={
'external_id': external_id,
'justificatifs': [
{
'b64_content': force_text(base64.b64encode(image_file)),
}
],
},
status=201,
)
assert response.json == {
'status': 'received',
'id': response.json['id'],
@ -97,20 +103,24 @@ def test_validation_pdf(app, admin, glc_app, john, pdf_file):
def test_many_attachments(app, admin, glc_app, john, png_file, jpeg_file, pdf_file):
external_id = uuid.uuid4().hex
response = glc_app.post_json('/api/users/%s/validate/' % john._oidc_sub, params={
'external_id': external_id,
'justificatifs': [
{
'b64_content': force_text(base64.b64encode(png_file)),
},
{
'b64_content': force_text(base64.b64encode(jpeg_file)),
},
{
'b64_content': force_text(base64.b64encode(pdf_file)),
},
],
}, status=201)
response = glc_app.post_json(
'/api/users/%s/validate/' % john._oidc_sub,
params={
'external_id': external_id,
'justificatifs': [
{
'b64_content': force_text(base64.b64encode(png_file)),
},
{
'b64_content': force_text(base64.b64encode(jpeg_file)),
},
{
'b64_content': force_text(base64.b64encode(pdf_file)),
},
],
},
status=201,
)
assert response.json == {
'status': 'received',
'id': response.json['id'],

View File

@ -1,5 +1,5 @@
import os.path
import datetime
import os.path
from django.core.files.base import ContentFile
from django.core.management import call_command
@ -8,12 +8,10 @@ from authentic2_cut import models
def test_validation_request_cleanup(db, admin, user, glc, freezer, png_file):
req = models.ValidationRequest.objects.create(
origin=glc.oidc_client,
user=user)
req = models.ValidationRequest.objects.create(origin=glc.oidc_client, user=user)
attachment = models.ValidationRequestAttachment.objects.create(
validation_request=req,
image=ContentFile(png_file, 'aaa'))
validation_request=req, image=ContentFile(png_file, 'aaa')
)
assert models.ValidationRequest.objects.count() == 1
assert models.ValidationRequestAttachment.objects.count() == 1
call_command('cleanupauthentic')

View File

@ -1,6 +1,5 @@
from django.urls import reverse
from authentic2.utils.misc import make_url
from django.urls import reverse
def login(app, user, path=None, password=None, remember_me=None):