From c8ed258f0605ecb651f2e8df44e7d501ddff7d95 Mon Sep 17 00:00:00 2001 From: Flavio Tarsetti <flavio.tarsetti@idiap.ch> Date: Tue, 27 Jun 2017 16:52:23 +0200 Subject: [PATCH] [accounts/ui/utils/navigation] added remove supervisee endpoint for godfather + management to block users + patched key relation for supervisee + started new endpoint to make request for supervision --- beat/web/accounts/api.py | 194 +++++++++++++++++- beat/web/accounts/api_urls.py | 11 + .../commands/block_rejected_users.py | 101 +++++++++ .../commands/clean_invalid_users.py | 2 +- .../0007_profile_first_rejection_date.py | 20 ++ .../migrations/0008_auto_20170626_1516.py | 20 ++ .../migrations/0009_auto_20170627_0956.py | 22 ++ beat/web/accounts/models.py | 3 +- beat/web/navigation/admin.py | 4 + beat/web/settings/settings.py | 1 + .../mail.godfather_rejected.message.txt | 17 ++ .../mail.godfather_rejected.subject.txt | 1 + ...father_rejected_delete_account.message.txt | 9 + .../management/commands/daily_cron_actions.py | 3 + 14 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 beat/web/accounts/management/commands/block_rejected_users.py create mode 100644 beat/web/accounts/migrations/0007_profile_first_rejection_date.py create mode 100644 beat/web/accounts/migrations/0008_auto_20170626_1516.py create mode 100644 beat/web/accounts/migrations/0009_auto_20170627_0956.py create mode 100644 beat/web/ui/registration/templates/registration/mail.godfather_rejected.message.txt create mode 100644 beat/web/ui/registration/templates/registration/mail.godfather_rejected.subject.txt create mode 100644 beat/web/ui/registration/templates/registration/mail.godfather_rejected_delete_account.message.txt diff --git a/beat/web/accounts/api.py b/beat/web/accounts/api.py index ae449df78..cfa2c90db 100644 --- a/beat/web/accounts/api.py +++ b/beat/web/accounts/api.py @@ -52,12 +52,13 @@ from .models import SupervisionTrack, Profile from ..common.models import Shareable from ..common.exceptions import ShareError from ..common.mixins import CommonContextMixin +from ..ui.registration.models import RegistrationProfile from itertools import chain from datetime import datetime, timedelta -from .permissions import IsGodfatherAndAuthor +from .permissions import IsGodfatherAndAuthor, IsAuthorAndNotGodfather from ..common.responses import BadRequestResponse, ForbiddenResponse @@ -168,3 +169,194 @@ class GodfatherAddSuperviseeView(BaseUpdateSupervisionTrackView): #---------------------------------------------------------- + + +class GodfatherRemoveSuperviseeView(BaseUpdateSupervisionTrackView): + permission_classes = BaseUpdateSupervisionTrackView.permission_classes + + def put(self, request, supervisee_name): + supervisee = User.objects.get(username=supervisee_name) + supervisiontrack = SupervisionTrack.objects.get(godfather=request.user, supervisee=supervisee, supervisee__profile__supervision_key=models.F('supervision_key')) + + from django.core.mail import send_mail + + parsed_url = urlparse(settings.URL_PREFIX) + server_address = '%s://%s' % (parsed_url.scheme, parsed_url.hostname) + + c = Context({ 'supervisor': supervisiontrack.godfather, + 'supervisee': supervisee, + 'prefix': server_address, + }) + + if supervisee.profile.status == Profile.WAITINGVALIDATION: + #New user account waiting validation, so delete this account and inform by email the user + try: + t = loader.get_template('registration/mail.godfather_rejected.subject.txt') + subject = t.render(c) + + # Note: e-mail subject *must not* contain newlines + subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines()) + + t = loader.get_template('registration/mail.godfather_rejected_delete_account.message.txt') + message = t.render(c) + + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [supervisee.email]) + except: + pass + + registration_profile = RegistrationProfile.objects.get(user=supervisee) + supervisee.profile.delete() + supervisee.delete() + supervisiontrack.delete() + registration_profile.delete() + else: + #Reject this account and inform by email the user + now = datetime.datetime.now() + expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_BLOCKAGE_AFTER_FIRST_REJECTION_DAYS) + + + supervisiontrack.expiration_date = now + supervisiontrack.is_valid = False + + supervisee.profile.status = Profile.REJECTED + supervisee.profile.supervision_key = None + if supervisee.profile.rejection_date == None: + supervisee.profile.rejection_date = now + expiration_date_delta + + supervisiontrack.save() + supervisee.profile.save() + supervisee.save() + + try: + t = loader.get_template('registration/mail.godfather_rejected.subject.txt') + subject = t.render(c) + + # Note: e-mail subject *must not* contain newlines + subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines()) + + t = loader.get_template('registration/mail.godfather_rejected.message.txt') + message = t.render(c) + + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [supervisee.email]) + except: + pass + + #New supervision request refused + if supervisiontrack.start_date is None: + supervisiontrack.delete() + + return Response(status=status.HTTP_204_NO_CONTENT) + + +##---------------------------------------------------------- +# +# +#class BaseCreateSupervisionTrackViewSupervisee(generics.CreateAPIView): +# model = SupervisionTrack +# serializer_class = SupervisionTrackUpdateSerializer +# +# def get_permissions(self): +# permission_classes = [permissions.IsAuthenticated, IsAuthorAndNotGodfather] +# +# self.permission_classes = permission_classes +# +# return super(BaseCreateSupervisionTrackViewSupervisee, self).get_permissions() +# +# +##---------------------------------------------------------- +# +# +#class SuperviseeAddGodfatherView(BaseCreateSupervisionTrackViewSupervisee): +# permission_classes = BaseCreateSupervisionTrackViewSupervisee.permission_classes +# +# def post(self, request, supervisor_name): +# godfather = User.objects.get(username=supervisor_name) +# supervisee = request.user +# print godfather +# print supervisee +# supervisee.profile.supervision_key = supervisee.profile._generate_current_supervision_key() +# #godfather = User.objects.get(username = self.cleaned_data['godfather']) +# supervisiontrack = SupervisionTrack.objects.create( +# supervisee = supervisee, +# godfather = godfather, +# is_valid = False, +# ) +# +# #Assign key to supervision track +# supervisiontrack.supervision_key = supervisee.profile.supervision_key +# supervisiontrack.save() +# supervisee.profile.supervision.add(supervisiontrack) +# supervisee.save() +# +# #supervisiontrack = SupervisionTrack.objects.get(godfather=request.user, supervisee=supervisee, supervisee__profile__supervision_key=models.F('supervision_key')) +# +# #from django.core.mail import send_mail +# +# #parsed_url = urlparse(settings.URL_PREFIX) +# #server_address = '%s://%s' % (parsed_url.scheme, parsed_url.hostname) +# +# #c = Context({ 'supervisor': supervisiontrack.godfather, +# # 'supervisee': supervisee, +# # 'prefix': server_address, +# # }) +# +# #if supervisee.profile.status == Profile.WAITINGVALIDATION: +# # #New user account waiting validation, so delete this account and inform by email the user +# # try: +# # t = loader.get_template('registration/mail.godfather_rejected.subject.txt') +# # subject = t.render(c) +# +# # # Note: e-mail subject *must not* contain newlines +# # subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines()) +# +# # t = loader.get_template('registration/mail.godfather_rejected_delete_account.message.txt') +# # message = t.render(c) +# +# # send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [supervisee.email]) +# # except: +# # pass +# +# # registration_profile = RegistrationProfile.objects.get(user=supervisee) +# # supervisee.profile.delete() +# # supervisee.delete() +# # supervisiontrack.delete() +# # registration_profile.delete() +# #else: +# # #Reject this account and inform by email the user +# # now = datetime.datetime.now() +# # expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_BLOCKAGE_AFTER_FIRST_REJECTION_DAYS) +# +# +# # supervisiontrack.expiration_date = now +# # supervisiontrack.is_valid = False +# +# # supervisee.profile.status = Profile.REJECTED +# # supervisee.profile.supervision_key = None +# # if supervisee.profile.rejection_date == None: +# # supervisee.profile.rejection_date = now + expiration_date_delta +# +# # supervisiontrack.save() +# # supervisee.profile.save() +# # supervisee.save() +# +# # try: +# # t = loader.get_template('registration/mail.godfather_rejected.subject.txt') +# # subject = t.render(c) +# +# # # Note: e-mail subject *must not* contain newlines +# # subject = settings.EMAIL_SUBJECT_PREFIX + ''.join(subject.splitlines()) +# +# # t = loader.get_template('registration/mail.godfather_rejected.message.txt') +# # message = t.render(c) +# +# # send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [supervisee.email]) +# # except: +# # pass +# +# # #if supervisiontrack.start_date is None: +# # # supervisiontrack.delete() +# +# return Response(status=status.HTTP_204_NO_CONTENT) +# +# +##---------------------------------------------------------- diff --git a/beat/web/accounts/api_urls.py b/beat/web/accounts/api_urls.py index a912e700d..1b09dc243 100644 --- a/beat/web/accounts/api_urls.py +++ b/beat/web/accounts/api_urls.py @@ -41,4 +41,15 @@ urlpatterns = [ name='validate_supervisee' ), + url( + r'^(?P<supervisee_name>[\w\W]+)/remove/$', + api.GodfatherRemoveSuperviseeView.as_view(), + name='remove_supervisee' + ), + + url( + r'^(?P<supervisor_name>[\w\W]+)/add/$', + api.SuperviseeAddGodfatherView.as_view(), + name='add_supervisor' + ), ] diff --git a/beat/web/accounts/management/commands/block_rejected_users.py b/beat/web/accounts/management/commands/block_rejected_users.py new file mode 100644 index 000000000..a00ffddf7 --- /dev/null +++ b/beat/web/accounts/management/commands/block_rejected_users.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# encoding: utf-8 + +############################################################################### +# # +# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.web module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # +# or FITNESS FOR A PARTICULAR PURPOSE. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + + +from django.core.management.base import BaseCommand, CommandError + +from datetime import date +import datetime + +from django.contrib.auth.models import User +from django.conf import settings + +from ...models import SupervisionTrack +from ...models import Profile +from ....ui.registration.models import RegistrationProfile + +import sys +import random + +class Command(BaseCommand): + + help = 'Block rejected users after rejection date with no valid supervisor' + + def add_arguments(self, parser): + parser.add_argument('--noinput', action='store_false', dest='interactive', default=False, + help=('Tells Django to NOT prompt the user for input of any kind.')) + + + def handle(self, *args, **options): + block = True + + if options['interactive']: + try: + answer = self.get_input_data('Block rejected user(s) that have not been validated by a supervisor? (y/n)? ', 'y').lower() + except KeyboardInterrupt: + self.stderr.write("\nOperation canceled.") + sys.exit(1) + + if answer != 'y': + self.stdout.write('Block users operation canceled') + sys.exit(1) + + if block: + rejected_profiles = Profile.objects.filter(status=Profile.REJECTED) + count = 0 + for rejected_profile in rejected_profiles: + user = rejected_profile.user + if user.profile.rejection_date < datetime.datetime.now(): + count +=1 + user.profile.status = Profile.BLOCKED + user.profile.rejection_date = None + user.is_active = False + + if user.profile.supervision_key != None: + supervisiontrack = SupervisionTrack.objects.get(supervision_key=rejected_profile.supervision_key) + if supervisiontrack.is_valid == False and supervisiontrack.start_date is None: + user.profile.supervision_key = None + supervisiontrack.delete() + + user.profile.save() + user.save() + + self.stdout.write('{} Rejected user(s) successfully blocked/'.format(count) + '{} Total user(s) checked'.format(rejected_profiles.count())) + + + def get_input_data(self, message, default=None): + """ + Override this method if you want to customize data inputs or + validation exceptions. + """ + raw_value = raw_input(message) + + if default and raw_value == '': + raw_value = default + + return raw_value diff --git a/beat/web/accounts/management/commands/clean_invalid_users.py b/beat/web/accounts/management/commands/clean_invalid_users.py index 5b2308ba4..36c3059e2 100644 --- a/beat/web/accounts/management/commands/clean_invalid_users.py +++ b/beat/web/accounts/management/commands/clean_invalid_users.py @@ -95,7 +95,7 @@ class Command(BaseCommand): supervisiontrack.delete() registration_profile.delete() - self.stdout.write('{} Invalid user(s) successfully cleaned/'.format(count) + '{} Total user(s) checked:'.format(invalid_userprofiles_new_users.count()+invalid_userprofiles_waiting_validation.count())) + self.stdout.write('{} Invalid user(s) successfully cleaned/'.format(count) + '{} Total user(s) checked'.format(invalid_userprofiles_new_users.count()+invalid_userprofiles_waiting_validation.count())) def get_input_data(self, message, default=None): diff --git a/beat/web/accounts/migrations/0007_profile_first_rejection_date.py b/beat/web/accounts/migrations/0007_profile_first_rejection_date.py new file mode 100644 index 000000000..ff5cac206 --- /dev/null +++ b/beat/web/accounts/migrations/0007_profile_first_rejection_date.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2017-06-26 14:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0006_auto_20170608_1451'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='first_rejection_date', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/beat/web/accounts/migrations/0008_auto_20170626_1516.py b/beat/web/accounts/migrations/0008_auto_20170626_1516.py new file mode 100644 index 000000000..f1ac03523 --- /dev/null +++ b/beat/web/accounts/migrations/0008_auto_20170626_1516.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2017-06-26 15:16 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0007_profile_first_rejection_date'), + ] + + operations = [ + migrations.RenameField( + model_name='profile', + old_name='first_rejection_date', + new_name='rejection_date', + ), + ] diff --git a/beat/web/accounts/migrations/0009_auto_20170627_0956.py b/beat/web/accounts/migrations/0009_auto_20170627_0956.py new file mode 100644 index 000000000..00e134aec --- /dev/null +++ b/beat/web/accounts/migrations/0009_auto_20170627_0956.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2017-06-27 09:56 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0008_auto_20170626_1516'), + ] + + operations = [ + migrations.AlterField( + model_name='supervisiontrack', + name='supervisee', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisors', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/beat/web/accounts/models.py b/beat/web/accounts/models.py index 8a3ddf0a5..e212ba512 100644 --- a/beat/web/accounts/models.py +++ b/beat/web/accounts/models.py @@ -50,7 +50,7 @@ class SupervisionTrack(models.Model): #_____ Fields __________ - supervisee = models.OneToOneField(User, on_delete=models.CASCADE, + supervisee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='supervisors') godfather = models.ForeignKey(User, on_delete=models.CASCADE, related_name='supervisees') @@ -97,6 +97,7 @@ class Profile(models.Model): supervision = models.ManyToManyField(SupervisionTrack, related_name='profiles', blank=True) supervision_key = models.CharField(max_length=40, null=True, blank=True) registration_date = models.DateTimeField(null=True, blank=True) + rejection_date = models.DateTimeField(null=True, blank=True) def __unicode__(self): return u'User: %s' % self.user.username diff --git a/beat/web/navigation/admin.py b/beat/web/navigation/admin.py index 726b02598..86f7b76f1 100644 --- a/beat/web/navigation/admin.py +++ b/beat/web/navigation/admin.py @@ -99,6 +99,8 @@ class UserAdmin(UserAdmin): def supervision_key(self, obj): return obj.profile.supervision_key + def rejection_date(self, obj): + return obj.profile.rejection_date list_display = ( @@ -112,6 +114,7 @@ class UserAdmin(UserAdmin): 'status', 'supervision', 'supervision_key', + 'rejection_date', ) ordering = ( @@ -166,6 +169,7 @@ class ProfileAdmin(admin.ModelAdmin): 'registration_date', 'is_godfather', 'supervision_key', + 'rejection_date', ) ordering = ( diff --git a/beat/web/settings/settings.py b/beat/web/settings/settings.py index 9c54130b0..6dfc77da8 100755 --- a/beat/web/settings/settings.py +++ b/beat/web/settings/settings.py @@ -218,6 +218,7 @@ DATASETS_ROOT_PATH = None ACCOUNT_ACTIVATION_DAYS = 2 ACCOUNT_ACTIVATION_DAYS_FROM_GODFATHER = 7 ACCOUNT_EXPIRATION_DAYS = 365 +ACCOUNT_BLOCKAGE_AFTER_FIRST_REJECTION_DAYS = 56 LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/' diff --git a/beat/web/ui/registration/templates/registration/mail.godfather_rejected.message.txt b/beat/web/ui/registration/templates/registration/mail.godfather_rejected.message.txt new file mode 100644 index 000000000..bb95d0708 --- /dev/null +++ b/beat/web/ui/registration/templates/registration/mail.godfather_rejected.message.txt @@ -0,0 +1,17 @@ +Dear {{ supervisee.first_name }}, + +Your supervision request was not accepted and has been rejected by the supervisor {{ supervisor.username }}. + +Either you made a mistake or you are not a supervisee anymore for {{ supervisor.username }} + +Your account {{ supervisee.username }} is currently not fully validated. + +You can request to validate your account through a new supervision request +either through an existing validated supervisor from the platform or by asking your current supervisor +to register on the platform as a supervisor and when his account is validated, +you can request a supervision from him and get a re-activation of your +account. + +Your account will be blocked on {{ supervisee.profile.rejection_date }}. You have until this date to find a +supervisor. Passed this date your account will be blocked and you will need to +proceed with an unblock account request. diff --git a/beat/web/ui/registration/templates/registration/mail.godfather_rejected.subject.txt b/beat/web/ui/registration/templates/registration/mail.godfather_rejected.subject.txt new file mode 100644 index 000000000..09ffd3d8c --- /dev/null +++ b/beat/web/ui/registration/templates/registration/mail.godfather_rejected.subject.txt @@ -0,0 +1 @@ +Account not confirmed - Supervision Rejected diff --git a/beat/web/ui/registration/templates/registration/mail.godfather_rejected_delete_account.message.txt b/beat/web/ui/registration/templates/registration/mail.godfather_rejected_delete_account.message.txt new file mode 100644 index 000000000..ca0d45a1a --- /dev/null +++ b/beat/web/ui/registration/templates/registration/mail.godfather_rejected_delete_account.message.txt @@ -0,0 +1,9 @@ +Dear {{ supervisee.first_name }}, + +Your supervision request was not accepted and has been rejected by the supervisor {{ supervisor.username }}. + +Either you made a mistake or you are not a supervisee of {{ supervisor.username }} + +Your newly created account {{ supervisee.username }} has therefore currently been deleted. + +You can re-create a new account and make a proper supervision request. diff --git a/beat/web/utils/management/commands/daily_cron_actions.py b/beat/web/utils/management/commands/daily_cron_actions.py index 34bcd0140..b2d7a7baa 100644 --- a/beat/web/utils/management/commands/daily_cron_actions.py +++ b/beat/web/utils/management/commands/daily_cron_actions.py @@ -55,3 +55,6 @@ class Command(BaseCommand): # Clean and remove invalid users call_command('clean_invalid_users') + + # Block rejected users with no supervision after specific rejection date + call_command('block_rejected_users') -- GitLab