Commit e419a2f64dd942d7e7553e8dd3d02ba9a5c0f4f7

Authored by Alexis Koralewski
1 parent 0edbf5e5
Exists in dev

Add new version for user management (almost complete)

src/core/pyros_django/user_manager/forms.py
1   -from django import forms
2   -from common.models import PyrosUser, Country, UserLevel, ScientificProgram
3   -from django.core.mail import send_mail
4   -from django.urls import reverse
5   -from django.conf import settings
6   -
7   -class PyrosUserCreationForm(forms.ModelForm):
8   - '''
9   - Form used at user creation.
10   -
11   - IMPORTANT : The user must not be able to choose his Country, Scientific Program(s), Quota, Priority, and User Level
12   - '''
13   -
14   - email = forms.EmailField(label="Email", widget=forms.TextInput)
15   -
16   - password = forms.CharField(label='Password', widget=forms.PasswordInput)
17   - password_confirm = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
18   -
19   - first_name = forms.CharField(label='First name')
20   - last_name = forms.CharField(label='Last name')
21   -
22   - class Meta:
23   - model = PyrosUser
24   - fields = ('tel', 'laboratory', 'address')
25   -
26   - def __init__(self, *args, **kwargs):
27   - super(PyrosUserCreationForm, self).__init__(*args, **kwargs)
28   - self.fields.move_to_end('tel', True)
29   - self.fields.move_to_end('laboratory', True)
30   - self.fields.move_to_end('address', True)
31   - for field in self.fields.values():
32   - field.required = True
33   - field.widget.attrs['class'] = "form-control"
34   -
35   - def clean_password_confirm(self):
36   - '''
37   - Checks if password and confirmation are equal
38   - '''
39   - password = self.cleaned_data.get("password")
40   - password_confirmation = self.cleaned_data.get("password_confirm")
41   - if password and password_confirmation and password != password_confirmation:
42   - raise forms.ValidationError("Passwords don't match")
43   - return password_confirmation
44   -
45   - def clean_email(self):
46   - '''
47   - Checks if the email already exists in DB
48   - '''
49   - email = self.cleaned_data.get("email")
50   - if PyrosUser.objects.filter(email=email).exists():
51   - raise forms.ValidationError("Email address already taken")
52   - return email
53   -
54   - def save(self):
55   - '''
56   - Creates a User and a PyrosUser in DB
57   - '''
58   - pyros_user = PyrosUser.objects.create(username=self.cleaned_data['email'], email=self.cleaned_data['email'], country=Country.objects.all()[0],
59   - tel=self.cleaned_data['tel'], laboratory=self.cleaned_data['laboratory'],
60   - address=self.cleaned_data['address'])
61   - pyros_user.set_password(self.cleaned_data['password'])
62   - pyros_user.first_name = self.cleaned_data['first_name']
63   - pyros_user.last_name = self.cleaned_data['last_name']
64   -
65   - # associate user_level Visitor to user
66   - UserLevel.objects.get(name = "Visitor").pyros_users.add(pyros_user)
67   - pyros_user.save()
68   -
69   - # get list of admin users
70   - admin_users = PyrosUser.objects.filter(user_level__priority=7).values_list("email",flat=True)
71   -
72   - # sending mail to new user
73   - send_mail(
74   - '[PyROS CC] Registration',
75   - 'Hi,\n\nThanks for your registration. The PI will examinate your account as soon as possible.\n\nCordially,\n\nPyROS Control Center',
76   - '',
77   - [self.cleaned_data['email']],
78   - fail_silently=False,
79   - )
80   -
81   - domain = settings.DEFAULT_DOMAIN
82   - url = f"{domain}{reverse('user-detail',args=(pyros_user.pk,))}"
83   -
84   - # sending mail to admin
85   - send_mail(
86   - '[PyROS CC] New registration',
87   - f"Hi,\n\nA new user : {self.cleaned_data['email']}, need a validation for his registration.\nClick on the following link to verify this user profile : {url} \n\nCordially,\n\nPyROS Control Center",
88   - '',
89   - admin_users,
90   - fail_silently=False,
91   - )
92   - return pyros_user
  1 +from django import forms
  2 +from common.models import PyrosUser, Country, UserLevel, Institute
  3 +from django.core.mail import send_mail
  4 +from django.urls import reverse
  5 +from django.conf import settings
  6 +from django.contrib.auth.forms import PasswordResetForm
  7 +
  8 +class PyrosUserCreationForm(forms.ModelForm):
  9 + '''
  10 + Form used at user creation.
  11 +
  12 + IMPORTANT : The user must not be able to choose his Country, Scientific Program(s), Quota, Priority, and User Level
  13 + '''
  14 +
  15 + email = forms.EmailField(label="Email", widget=forms.TextInput)
  16 +
  17 + password = forms.CharField(label='Password', widget=forms.PasswordInput)
  18 + password_confirm = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
  19 +
  20 + first_name = forms.CharField(label='First name')
  21 + last_name = forms.CharField(label='Last name')
  22 + institute = forms.ModelChoiceField(queryset=Institute.objects.all())
  23 + reason = forms.CharField(widget=forms.Textarea,label="Motive of registration")
  24 + class Meta:
  25 + model = PyrosUser
  26 + fields = ('tel', 'laboratory', 'address')
  27 +
  28 + def __init__(self, *args, **kwargs):
  29 + super(PyrosUserCreationForm, self).__init__(*args, **kwargs)
  30 + self.fields.move_to_end('tel', True)
  31 + self.fields.move_to_end('laboratory', True)
  32 + self.fields.move_to_end('address', True)
  33 + for field in self.fields.values():
  34 + field.required = True
  35 + field.widget.attrs['class'] = "form-control"
  36 + self.fields["roles"] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, required
  37 + =False,queryset=UserLevel.objects.all(),to_field_name="name", label="Wished role(s)")
  38 +
  39 + def clean_password_confirm(self):
  40 + '''
  41 + Checks if password and confirmation are equal
  42 + '''
  43 + password = self.cleaned_data.get("password")
  44 + password_confirmation = self.cleaned_data.get("password_confirm")
  45 + if password and password_confirmation and password != password_confirmation:
  46 + raise forms.ValidationError("Passwords don't match")
  47 + return password_confirmation
  48 +
  49 + def clean_email(self):
  50 + '''
  51 + Checks if the email already exists in DB
  52 + '''
  53 + email = self.cleaned_data.get("email")
  54 + if PyrosUser.objects.filter(email=email).exists():
  55 + raise forms.ValidationError("Email address already taken")
  56 + return email
  57 +
  58 + def save(self):
  59 + '''
  60 + Creates a User and a PyrosUser in DB
  61 + '''
  62 + pyros_user = PyrosUser.objects.create(username=self.cleaned_data['email'], email=self.cleaned_data['email'], country=Country.objects.all()[0],
  63 + tel=self.cleaned_data['tel'], laboratory=self.cleaned_data['laboratory'],
  64 + address=self.cleaned_data['address'], institute=self.cleaned_data["institute"], motive_of_registration=self.cleaned_data["reason"])
  65 + pyros_user.set_password(self.cleaned_data['password'])
  66 + pyros_user.first_name = self.cleaned_data['first_name']
  67 + pyros_user.last_name = self.cleaned_data['last_name']
  68 +
  69 + # associate user_level Visitor to user
  70 + UserLevel.objects.get(name = "Visitor").pyros_users.add(pyros_user)
  71 + pyros_user.save()
  72 +
  73 + # get list of admin and Unit-PI users
  74 + admin_and_unit_PI_users = PyrosUser.objects.filter(user_level__priority__gte=6).distinct().values_list("email",flat=True)
  75 +
  76 + # sending mail to new user
  77 + send_mail(
  78 + '[PyROS CC] Registration',
  79 + 'Hi,\n\nThanks for your registration. The PI will examinate your account as soon as possible.\n\nCordially,\n\nPyROS Control Center',
  80 + '',
  81 + [self.cleaned_data['email']],
  82 + fail_silently=False,
  83 + )
  84 +
  85 + domain = settings.DEFAULT_DOMAIN
  86 + url = f"{domain}{reverse('user_detail',args=(pyros_user.pk,))}"
  87 + wished_roles = ""
  88 + for role in self.cleaned_data["roles"]:
  89 + wished_roles+= f"{role} "
  90 + # sending mail to admin
  91 + send_mail(
  92 + '[PyROS CC] New registration',
  93 + f"Hi,\n\nA new user : {self.cleaned_data['email']}, need a validation for his registration.\nClick on the following link to verify this user profile : {url}\n\nReason of the registration : {self.cleaned_data['reason']}\n\nWished role(s) : {wished_roles}\n\nCordially,\n\nPyROS Control Center",
  94 + '',
  95 + admin_and_unit_PI_users,
  96 + fail_silently=False,
  97 + )
  98 + return pyros_user
  99 +
  100 +class UserPasswordResetForm(PasswordResetForm):
  101 + def __init__(self, *args, **kwargs):
  102 + super(UserPasswordResetForm, self).__init__(*args, **kwargs)
  103 +
  104 + email = forms.EmailField(label='Email ', widget=forms.EmailInput(attrs={
  105 + 'placeholder': '',
  106 + 'type': 'text',
  107 + 'name': 'email'
  108 + }))
93 109 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/static/user_manager/css/user_detail.css 0 โ†’ 100644
... ... @@ -0,0 +1,92 @@
  1 +/* Float cancel and delete buttons and add an equal width */
  2 +
  3 +.modal-btn {
  4 + float: left;
  5 + width: 50%;
  6 +}
  7 +
  8 +
  9 +/* Add padding and center-align text to the container */
  10 +
  11 +.container {
  12 + padding: 16px;
  13 + text-align: center;
  14 +}
  15 +
  16 +
  17 +/* The Modal (background) */
  18 +
  19 +#modal_delete_user {
  20 + display: none;
  21 + /* Hidden by default */
  22 + position: fixed;
  23 + /* Stay in place */
  24 + z-index: 1;
  25 + /* Sit on top */
  26 + left: 0;
  27 + top: 0;
  28 + width: 100%;
  29 + /* Full width */
  30 + height: 100%;
  31 + /* Full height */
  32 + overflow: auto;
  33 + /* Enable scroll if needed */
  34 + background-color: #474e5d;
  35 + padding-top: 50px;
  36 +}
  37 +
  38 +
  39 +/* Modal Content/Box */
  40 +
  41 +.modal-content {
  42 + background-color: #fefefe;
  43 + margin: 5% auto 15% auto;
  44 + /* 5% from the top, 15% from the bottom and centered */
  45 + border: 1px solid #888;
  46 + width: 80%;
  47 + /* Could be more or less, depending on screen size */
  48 +}
  49 +
  50 +
  51 +/* Style the horizontal ruler */
  52 +
  53 +hr {
  54 + border: 1px solid #f1f1f1;
  55 + margin-bottom: 25px;
  56 +}
  57 +
  58 +
  59 +/* The Modal Close Button (x) */
  60 +
  61 +#close_modal_delete_user {
  62 + position: absolute;
  63 + right: 35px;
  64 + top: 15px;
  65 + font-size: 40px;
  66 + font-weight: bold;
  67 + color: #f1f1f1;
  68 +}
  69 +
  70 +.close:hover,
  71 +.close:focus {
  72 + color: #f44336;
  73 + cursor: pointer;
  74 +}
  75 +
  76 +
  77 +/* Clear floats */
  78 +
  79 +.clearfix::after {
  80 + content: "";
  81 + clear: both;
  82 + display: table;
  83 +}
  84 +
  85 +
  86 +/* Change styles for cancel button and delete button on extra small screens */
  87 +
  88 +@media screen and (max-width: 300px) {
  89 + .modal-btn {
  90 + width: 100%;
  91 + }
  92 +}
0 93 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/static/user_manager/js/user_detail.js 0 โ†’ 100644
... ... @@ -0,0 +1,12 @@
  1 +function changeActiveRole() {
  2 + csrf_value = $("[name='csrfmiddlewaretoken']").val();
  3 + console.log($("#change_active_role").attr('action'));
  4 + $.post($("#change_active_role").attr('action'), { "role": $("#selected_role option:selected").val(), csrfmiddlewaretoken: csrf_value }, function(data) {
  5 + $("#has_changed").html("").append(data);
  6 + });
  7 +
  8 +}
  9 +
  10 +$("#selected_role").change(function(event) {
  11 + changeActiveRole();
  12 +});
0 13 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/base_home.html
1   -{% load bootstrap3 %}
2   -
3   -{% bootstrap_css %}
4   -
5   -<!DOCTYPE html>
6   -<html lang="en">
7   - <head>
8   - {% load staticfiles %}
9   -
10   - <meta charset="utf-8">
11   - <meta http-equiv="X-UA-Compatible" content="IE=edge">
12   - <meta name="viewport" content="width=device-width, initial-scale=1">
13   - <meta name="description" content="">
14   - <meta name="author" content="">
15   -
16   - <title>Pyros - SVOM</title>
17   - </head>
18   -
19   - <body>
20   -
21   -
22   -
23   - <div class="container" style="padding-top: 50px;">
24   -
25   - <center>
26   -
27   -
28   - <div class="row">
29   -
30   -
31   - <div class="jumbotron">
32   - <a href="/">
33   - <h2>Welcome to Pyros : the French GTF Control Center</h2>
34   - </a>
35   - </div>
36   -
37   - <hr>
38   -
39   -
40   -
41   -
42   - <div class="row">
43   - {% if error %}
44   - <div class="alert alert-dismissable alert-danger">
45   - {{message}}
46   - {% endif %}
47   - {% if success %}
48   - <div class="alert alert-dismissable alert-success">
49   - {{message}}
50   - {% endif %}
51   -
52   - </div>
53   -
54   - </div>
55   -
56   -
57   - {% block content %}
58   -
59   -
60   - {% endblock %}
61   -
62   - </div>
63   - <div class="row">
64   - <p><br/></p>
65   - </div>
66   - <div class="row">
67   - <div class="col-lg-12 text-center" >
68   - OBSERVATOIRE MIDI-PYRENEES - 14, avenue Edouard Belin - 31400 TOULOUSE
69   - Tรฉl. +33 (0)5 61 33 29 29 - Fax : +33 (0)5 61 33 28 88
70   - Copyright ยฉ 2016 OMP - Tous droits rรฉservรฉs.
71   - Service web OMP - Mentions lรฉgales
72   - </div>
73   - </div>
74   - <div class="row">
75   - <p><br/></p>
76   - </div>
77   -
78   - </div>
79   -
80   - </center>
81   -
82   - </div>
83   -
84   -
85   -
86   -
87   -
88   -
89   - <!-- Bootstrap core JavaScript
90   - ================================================== -->
91   - <!-- Placed at the end of the document so the pages load faster -->
92   - <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
93   -
94   - </body>
  1 +{% load bootstrap3 %}
  2 +
  3 +{% bootstrap_css %}
  4 +
  5 +<!DOCTYPE html>
  6 +<html lang="en">
  7 + <head>
  8 + {% load staticfiles %}
  9 +
  10 + <meta charset="utf-8">
  11 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  12 + <meta name="viewport" content="width=device-width, initial-scale=1">
  13 + <meta name="description" content="">
  14 + <meta name="author" content="">
  15 +
  16 + <title>Pyros</title>
  17 + </head>
  18 +
  19 + <body>
  20 +
  21 +
  22 +
  23 + <div class="container" style="padding-top: 50px;">
  24 +
  25 + <center>
  26 +
  27 +
  28 + <div class="row">
  29 +
  30 +
  31 + <div class="jumbotron">
  32 + <a href="/">
  33 + <h2>Welcome to Pyros</h2>
  34 + </a>
  35 + </div>
  36 +
  37 + <hr>
  38 +
  39 +
  40 +
  41 +
  42 + <div class="row">
  43 + {% if error %}
  44 + <div class="alert alert-dismissable alert-danger">
  45 + {{message}}
  46 + {% endif %}
  47 + {% if success %}
  48 + <div class="alert alert-dismissable alert-success">
  49 + {{message}}
  50 + {% endif %}
  51 +
  52 + </div>
  53 +
  54 + </div>
  55 +
  56 +
  57 + {% block content %}
  58 +
  59 +
  60 + {% endblock %}
  61 +
  62 + </div>
  63 + <div class="row">
  64 + <p><br/></p>
  65 + </div>
  66 + <div class="row">
  67 + <div class="col-lg-12 text-center" >
  68 + OBSERVATOIRE MIDI-PYRENEES - 14, avenue Edouard Belin - 31400 TOULOUSE
  69 + Tรฉl. +33 (0)5 61 33 29 29 - Fax : +33 (0)5 61 33 28 88
  70 + Copyright ยฉ 2016 OMP - Tous droits rรฉservรฉs.
  71 + Service web OMP - Mentions lรฉgales
  72 + </div>
  73 + </div>
  74 + <div class="row">
  75 + <p><br/></p>
  76 + </div>
  77 +
  78 + </div>
  79 +
  80 + </center>
  81 +
  82 + </div>
  83 +
  84 +
  85 +
  86 +
  87 +
  88 +
  89 + <!-- Bootstrap core JavaScript
  90 + ================================================== -->
  91 + <!-- Placed at the end of the document so the pages load faster -->
  92 + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
  93 +
  94 + </body>
95 95 </html>
96 96 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/home_user_creation.html
1   -
2   - {% extends "user_manager/base_home.html" %}
3   -
4   - {% block content %}
5   -
6   -
7   - <div class="form-group">
8   -
9   -
10   - <form action="{% url "user_signup_validation" %}" method="post" enctype="multipart/form-data">
11   - {% csrf_token %}
12   - <div class="col-lg-3 text-left" >
13   - <p></p>
14   - </div>
15   - <div class="col-lg-6 text-left" >
16   - <div class="row">
17   - <h3>Subscription to Pyros</h3>
18   - <hr>
19   - </div>
20   - {% for field in form %}
21   - <div class="row">
22   -
23   - <label> {{ field.label_tag }} {% if field.field.required %}&nbsp*{% endif %} </label>
24   -
25   - {{ field }}
26   - {{ field.errors }}
27   - <br/>
28   - </div>
29   - {% endfor %}
30   - <br>
31   - <p>Once validated, your subscription will be sent to our team.</p>
32   - <p>You will be given an answer by email.</p>
33   -
34   - <p><input type="submit" class="btn btn-primary" value="Send subscription request" /></p>
35   - </div>
36   - </form>
37   -
38   - </div>
39   -
40   -
41   -
42   -
43   - {% endblock %}
  1 +
  2 + {% extends "user_manager/base_home.html" %}
  3 + {% load static %}
  4 + {% block content %}
  5 +
  6 + <div class="form-group">
  7 +
  8 +
  9 + <form action="{% url "user_signup_validation" %}" method="post" enctype="multipart/form-data">
  10 + {% csrf_token %}
  11 + {% if next %}
  12 + <input class="form-control" type="hidden" name="next" value="{{ next }}" />
  13 + {% endif %}
  14 + <div class="col-lg-3 text-left" >
  15 + <p></p>
  16 + </div>
  17 + <div class="col-lg-6 text-left" >
  18 + <div class="row">
  19 + <h3>Subscription to Pyros</h3>
  20 + <hr>
  21 + </div>
  22 + {% for field in form %}
  23 + <div class="row">
  24 +
  25 + <label> {{ field.label_tag }} {% if field.field.required %}&nbsp*{% endif %} </label>
  26 + {% if "role" in field.label_tag %}
  27 + <a href="{% url "roles_description" %}" target="_blank" rel="noopener noreferrer"> <img class="bi bi-info-circle" width="20" heigth="20" src="{% static "media/info-circle.svg" %}"/> </a>
  28 + {% endif %}
  29 + </div>
  30 + <div class="row">
  31 + {{ field }}
  32 + {{ field.errors }}
  33 + </div>
  34 + <br/>
  35 + {% endfor %}
  36 + <br>
  37 + <p>Once validated, your subscription will be sent to our team.</p>
  38 + <p>You will be given an answer by email.</p>
  39 +
  40 + <p><input type="submit" class="btn btn-primary" value="Send subscription request" /></p>
  41 + </div>
  42 + </form>
  43 +
  44 + </div>
  45 +
  46 +
  47 +
  48 +
  49 + {% endblock %}
... ...
src/core/pyros_django/user_manager/templates/user_manager/password_reset.html 0 โ†’ 100644
... ... @@ -0,0 +1,22 @@
  1 +{% extends 'base.html' %}
  2 +{% block content %}
  3 +
  4 +<div class="text-center" style="width: 80%; margin: 0 auto">
  5 + <h1>Welcome to the Password Reset Page</h1>
  6 + <h3>
  7 + Forgot your password? Please enter the email address you used to register
  8 + with us and we will send you a link to reset your password
  9 + </h3>
  10 +</div>
  11 +<br/>
  12 +
  13 +<form action="" method="POST" class="text-center">
  14 + {% csrf_token %}
  15 + {{form}}
  16 + <input type="submit" value="Send email" /><br>
  17 +</form><br/>
  18 +<div class="text-center">
  19 + <a href="{% url 'index' %}">Return to home page</a>
  20 +</div>
  21 +
  22 +{% endblock%}
0 23 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/password_reset_complete.html 0 โ†’ 100644
... ... @@ -0,0 +1,13 @@
  1 +{% extends 'base_unlogged.html' %}
  2 +{% block content%}
  3 +<div class="text-center" style="width: 80%; margin: 0 auto">
  4 + <h1>Password reset complete</h1>
  5 + <h6 style="width: 60%; margin: 0 auto">
  6 + Your Password has been set. You may go ahead and login
  7 + </h6>
  8 +</div>
  9 +<br />
  10 +<div class="text-center">
  11 + <a href="{% url 'user_signin' %}">Login</a>
  12 +</div>
  13 +{% endblock%}
0 14 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/password_reset_confirm.html 0 โ†’ 100644
... ... @@ -0,0 +1,16 @@
  1 +{% extends 'base_unlogged.html' %}
  2 +{% block content%}
  3 +<div class="text-center" style="width: 80%; margin: 0 auto">
  4 + <h1>Password Reset Form</h1>
  5 + <h6 style="width: 60%; margin: 0 auto">
  6 + Please enter your new password so we can verify.
  7 + </h6>
  8 +</div>
  9 +<br />
  10 +<form action="" method="POST">
  11 + {% csrf_token %}
  12 + {{form}}
  13 + <input type="submit" value="Reset Password"/>
  14 +</form>
  15 +
  16 +{% endblock%}
0 17 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/password_reset_done.html 0 โ†’ 100644
... ... @@ -0,0 +1,11 @@
  1 +{% extends 'base_unlogged.html' %}
  2 +{% block content%}
  3 +<div class="text-center" style="width: 80%; margin: 0 auto">
  4 + <h1>Password reset sent</h1>
  5 + <h5>Weโ€™ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.</h5><br>
  6 + <h6 style="width: 60%; margin: 0 auto">If you donโ€™t receive an email, please make sure youโ€™ve entered the address you registered with, and check your spam folder.</h6>
  7 +</div>
  8 +<div class="text-center">
  9 + <a href="{% url 'index' %}">Return to home page</a>
  10 +</div>
  11 +{% endblock%}
0 12 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/password_reset_email.html 0 โ†’ 100644
... ... @@ -0,0 +1,13 @@
  1 +{% autoescape off %}
  2 +To initiate the password reset process for your {{ user.get_username }} PyROS Account,
  3 +click the link below:
  4 +
  5 +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
  6 +
  7 +If clicking the link above doesn't work, please copy and paste the URL in a new browser
  8 +window instead.
  9 +
  10 +Sincerely,
  11 +
  12 +PyROS Control Center
  13 +{% endautoescape %}
0 14 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/roles_description.html 0 โ†’ 100644
... ... @@ -0,0 +1,48 @@
  1 +{% extends "user_manager/base_home.html" %}
  2 +{% block content %}
  3 +
  4 + <table class="table table-bordered">
  5 + <thead>
  6 + <tr>
  7 + <th>Role</th>
  8 + <th>Description</th>
  9 + </thead>
  10 + </tr>
  11 + <tbody>
  12 + <tr>
  13 + <td>Admin</td>
  14 + <td>User with all authorizations, can access to Django admin pages.</td>
  15 + </tr>
  16 + <tr>
  17 + <td>Observer</td>
  18 + <td>User that can create a scientific program or be an member of an scientific program to do observations.</td>
  19 + </tr>
  20 + <tr>
  21 + <td>TAC</td>
  22 + <td>Time Allocation Comity. User that will give his opinion about proposals (i.e. scientific programs that has been submitted by an Observer but not accepted by the Unit PI).</td>
  23 + </tr>
  24 + <tr>
  25 + <td>Management board member</td>
  26 + <td>User that represents an Institute.</td>
  27 + </tr>
  28 + <tr>
  29 + <td>Operator</td>
  30 + <td>User that is assigned to the maintenance of an Unit.</td>
  31 + </tr>
  32 + <tr>
  33 + <td>Unit-PI</td>
  34 + <td>Principal Investigator of the Unit.</td>
  35 + </tr>
  36 + <tr>
  37 + <td>Unit board</td>
  38 + <td>Help the Unit-PI to manage the unit.</td>
  39 + </tr>
  40 + <tr>
  41 + <td>Visitor</td>
  42 + <td>User without any privileges.</td>
  43 + </tr>
  44 + </tbody>
  45 +
  46 +</table>
  47 +
  48 +{% endblock %}
0 49 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/user_detail.html 0 โ†’ 100644
... ... @@ -0,0 +1,107 @@
  1 +{% extends "base.html" %}
  2 +
  3 +{% block content %}
  4 +{% load static %}
  5 +{% load tags %}
  6 +<link rel="stylesheet" type="text/css" href="{% static 'user_manager/css/user_detail.css' %}">
  7 +<script src="{% static "user_manager/js/user_detail.js" %}" defer></script>
  8 +<link rel="stylesheet" type="text/css" href="{% static '/css/global.css' %}">
  9 +<script src="{% static "/js/global.js" %}" defer></script>
  10 +
  11 + <h2>User : </h2>
  12 + <br> <br>
  13 + <p><strong>Name : </strong> {{ user.first_name }} {{ user.last_name }}</p>
  14 + <p><strong>Email : </strong> {{user.email }}</p>
  15 + <p><strong>Country : </strong>{{ user.country }}</p>
  16 + <p><strong>Role(s) : </strong>{{ user.get_roles_str }}</p>
  17 + {% if current_user.id is user.id %}
  18 + <p><strong>Active role : </strong>
  19 + {% if roles|length == 1 %}
  20 + {{ request.session.role }}
  21 + {% else %}
  22 + {# user visiting his own page to change active role #}
  23 + <form id="change_active_role" action="{% url 'set_active_role' %}" method="POST">
  24 + {% csrf_token %}
  25 + <select id="selected_role" class="form-select" name="selected_role">
  26 + {% for role in roles %}
  27 + {% if role == request.session.role %}
  28 + <option value="{{role}}" selected>{{role}}</option>
  29 + {% else %}
  30 + <option value="{{role}}">{{role}}</option>
  31 + {% endif %}
  32 + {% endfor %}
  33 + </select>
  34 + </form>
  35 + {# will display a text if changing role succeed #}
  36 + <p id="has_changed"></p>
  37 + {% endif %}
  38 + {% endif %}
  39 + <p><strong>Role Description : </strong>{{ user.get_max_priority_desc }}</p>
  40 + {% comment %}
  41 + <p><strong>Role Priority : </strong>{{ user.get_priority }}</p>
  42 + <p><strong>Max Level Quota : </strong>{{ user.get_max_priority_quota }}</p>
  43 + <p><strong>Description : </strong>{{ user.desc }}</p>
  44 + {% endcomment %}
  45 + <p><strong>Telephone : </strong>{{ user.tel }}</p>
  46 + <p><strong>Laboratory : </strong>{{ user.laboratory }}</p>
  47 + <p><strong>Address : </strong>{{ user.address }}</p>
  48 + <p><strong>Institute : </strong>{{ user.institute }}</p>
  49 + {% comment %}
  50 + <p><strong>Last connection : </strong>{{ user.last_connect }}</p>
  51 + <p><strong>Current connection : </strong>{{ user.cur_connect }}</p>
  52 + <p><strong>Put validation beginning : </strong>{{ user.putvalid_beg }}</p>
  53 + <p><strong>Put validation end : </strong>{{ user.putvalid_end }}</p>
  54 + <p><strong>Quota : </strong>{{ user.quota }}</p>
  55 + {% endcomment %}
  56 + {% if scientific_programs|length > 0 %}
  57 + <p><strong>Scientific Program(s) : </strong> <table class="table table-bordered table-hover table-striped" style="font-family: 'Montserra', sans-serif;">
  58 +
  59 + <thead>
  60 + <tr>
  61 + <th>Scientific Program</th>
  62 + </tr>
  63 + </thead>
  64 +
  65 + <tbody>
  66 + {% for sp in scientific_programs %}
  67 + <tr>
  68 + <td> <a href="{% url "detail_scientific_program" sp.pk %}">{{ sp }} </a></td>
  69 + </tr>
  70 + {% endfor %}
  71 + </tbody>
  72 + </table></p>
  73 + {% endif %}
  74 + {% if current_user.id is user.id or USER_LEVEL|ifinlist:"Admin,Unit-PI,Unit board"%}
  75 + <p><strong>Validator : </strong>{{ user.validator }}</p>
  76 + {% if user.motive_of_registration|length > 0 %}
  77 + <p><strong>Motive of registration : </strong>{{ user.motive_of_registration }}</p>
  78 + {% endif %}
  79 + <a href="{% url "user-edit" user.pk %}" class="btn btn-info" role="button">Edit</a>
  80 + {% if USER_LEVEL|ifinlist:"Admin,Unit-PI,Unit board" %}
  81 + {% if not is_last_user or not user.is_superuser%}
  82 + <a href="{% url "change_activate" user.pk current_user.id %}" class="btn btn-danger" role="button">{% active_account user.pk %} </a>
  83 + {% endif %}
  84 + <button type="button" class="btn btn-danger open-modal" id="open_modal_delete">Delete</button>
  85 + {# start of modal #}
  86 + <div id="modal_delete">
  87 + <span id="close_modal_delete" class="close">x</span>
  88 +
  89 + <form class="modal-content" action="{% url 'user-delete' user.id %}" method="post">
  90 + {% csrf_token %}
  91 + <div class="container">
  92 + <h1> Delete Account </h1>
  93 + <p>Are you sure you want to delete this account?</p>
  94 +
  95 + <div class="clearfix">
  96 + <button type="button" class="btn btn-info" id="cancel_modal_delete_btn">Cancel</button>
  97 + <input class="btn btn-danger delete_modal_delete_btn" type="submit" value="Delete user">
  98 + </div>
  99 + </div>
  100 + </form>
  101 + </div>
  102 + {# end of modal #}
  103 +
  104 + {% endif %}
  105 + {% endif %}
  106 + <a href="{% url "users" %}" class="btn btn-info" role="button">Return to list of users</a>
  107 +{% endblock %}
0 108 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/user_detail_edit.html 0 โ†’ 100644
... ... @@ -0,0 +1,40 @@
  1 +{% extends "base.html" %}
  2 +{% load tags %}
  3 +{% block content %}
  4 +<form id="siteForm" action="" method="post">
  5 + {% csrf_token %}
  6 + {{ form.as_p }}
  7 + {% if current_user.id is user.id or USER_LEVEL|ifinlist:"Admin,Unit-PI,Unit board" %}
  8 + <label> Roles : </label>
  9 + {% for role in roles %}
  10 + <br>
  11 + {% comment %}
  12 + input name is the same for all role so we can retrieve a list of selected values
  13 + {% endcomment %}
  14 + {# if user is sp_pi, he can have the "unit board" role #}
  15 + {% if role.name == "Unit board" and is_sp_pi %}
  16 +
  17 + {% if role.name in user_edit.get_roles_str %}
  18 + <input type="checkbox" checked id="{{role.name}}" name="roles" value="{{role.id}}">
  19 + <label for="{{role.name}}">{{role.name}}</label>
  20 + {% else %}
  21 + <input type="checkbox" id="{{role.name}}" name="roles" value="{{role.id}}">
  22 + <label for="{{role.name}}">{{role.name}}</label>
  23 + {% endif %}
  24 + {% else %}
  25 + {% if role.name in user_edit.get_roles_str %}
  26 + <input type="checkbox" checked id="{{role.name}}" name="roles" value="{{role.id}}">
  27 + <label for="{{role.name}}">{{role.name}}</label>
  28 + {% else %}
  29 + <input type="checkbox" id="{{role.name}}" name="roles" value="{{role.id}}">
  30 + <label for="{{role.name}}">{{role.name}}</label>
  31 + {% endif %}
  32 + {% endif %}
  33 + {% endfor %}
  34 + {% endif %}
  35 + <br>
  36 + <input id="Update" class="btn btn-info" type="submit" value="Update" />
  37 + <a href="{% url "user_detail" pk %}" class="btn btn-info" role="button">Cancel</a>
  38 +</form>
  39 +<a href="{% url "password_reset" %}" class="btn btn-info" role="button">Reset password</a>
  40 +{% endblock %}
0 41 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/templates/user_manager/users_management.html 0 โ†’ 100644
... ... @@ -0,0 +1,89 @@
  1 +{% extends "base.html" %}
  2 +{% load tags %}
  3 +{% block title %}
  4 + PYROS USERS MANAGEMENT
  5 +{% endblock %}
  6 +
  7 +{% block content %}
  8 +<style>
  9 + #div_users {
  10 + padding-left: 5%;
  11 + padding-top: 5%;
  12 + }
  13 + a {
  14 + color: inherit;
  15 + }
  16 +</style>
  17 +
  18 +<div id="div_users"class="row">
  19 + <h3>List of active users </h3>
  20 + {% if user.get_priority >= 6 %}
  21 + <a href="{% url "create_user" %}" class="btn btn-info" role="button">Create new user </a>
  22 + {% endif %}
  23 + <div class="table-responsive">
  24 + <table class="table table-bordered table-hover table-striped tablesorter" style="font-family: 'Montserra', sans-serif;">
  25 + <thead>
  26 + <tr>
  27 + <th>Name <i class="fa fa-sort"></i></th>
  28 + <th>Roles</th>
  29 + <th>Laboratory </th>
  30 + <th colspan="{{ nb_of_scientific_program }}">Scientific programs</th>
  31 + </tr>
  32 + </thead>
  33 + <tbody>
  34 + {% for field in pyros_users_with_roles %}
  35 + <tr>
  36 + <td> <a href="{% url "user_detail" field.pk %}"> {{ field.username }} </a></td>
  37 + <td>{{ field.get_roles_str}}</td>
  38 + <td> {{ field.laboratory }} </td>
  39 + <td> {{field.get_scientific_programs_str }}</td>
  40 + {% comment %}
  41 + {% for sp in field.scientific_programs.all %}
  42 +
  43 + {% if forloop.last and forloop.counter < nb_of_scientific_program %}
  44 + {% comment "" %}
  45 + we need to fill the remaining colspan size
  46 +
  47 +
  48 +
  49 + {% else %}
  50 + {% endif %}
  51 + <td> <a href="{% url "detail_scientific_program" sp.pk %}"> {{ sp }} </a> </td>
  52 + {% endfor %}
  53 + {% endcomment %}
  54 +
  55 + </td>
  56 + </tr>
  57 + {% endfor %}
  58 + </tbody>
  59 + </table>
  60 + </div>
  61 +</div>
  62 +{% if pyros_users_without_roles.count > 0 %}
  63 +<div id="div_users"class="row">
  64 + <h3>List of users that need role assignation </h3>
  65 + <div class="table-responsive">
  66 + <table class="table table-bordered table-hover table-striped tablesorter" style="font-family: 'Montserra', sans-serif;">
  67 + <thead>
  68 + <tr>
  69 + <th>Name <i class="fa fa-sort"></i></th>
  70 + <th>Roles</th>
  71 + <th>Laboratory </th>
  72 + </tr>
  73 + </thead>
  74 + <tbody>
  75 + {% for field in pyros_users_without_roles %}
  76 + <tr>
  77 + <td> <a href="{% url "user_detail" field.pk %}"> {{ field.username }} </a></td>
  78 + <td>{{ field.get_roles_str}}</td>
  79 + <td> {{ field.laboratory }} </td>
  80 +
  81 + </td>
  82 + </tr>
  83 + {% endfor %}
  84 + </tbody>
  85 + </table>
  86 + </div>
  87 +</div>
  88 +{% endif %}
  89 +{% endblock %}
0 90 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/tests.py
1   -from django.test import TestCase
2   -from common.models import *
3   -from django.contrib.auth.models import User
4   -
5   -
6   -
7   -class UserManagerTests(TestCase):
8   -
9   - def setUp(self):
10   - UserLevel.objects.create(name="Visitor",desc="Visitor description",priority=0,quota=0.0)
11   - UserLevel.objects.create(name="PI",desc="PI description",priority=5,quota=100.0)
12   - UserLevel.objects.create(name="SysAdmin",desc="SysAdmin description",priority=7,quota=1000.0)
13   - Country.objects.create()
14   - self.assertEqual(PyrosUser.objects.count(), 0, "There should be no User")
15   - path = "/user_manager/creation_validate"
16   - response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze", "password_confirm": "aze",
17   - "first_name": "toto", "last_name": "titi", "tel": "0123456789",
18   - "laboratory": "IRAP", "address": "ici"})
19   - self.assertTrue("success" in response.context.keys(), "There should be a success")
20   - self.assertEqual(PyrosUser.objects.count(), 1, "Theroue shld be one User")
21   -
22   - def test_creation(self):
23   - self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
24   - self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
25   -
26   - self.assertEqual(PyrosUser.objects.all()[0].first_name, 'toto')
27   - self.assertEqual(PyrosUser.objects.all()[0].email, 'toto@titi.fr')
28   - self.assertEqual(PyrosUser.objects.all()[0].user_level.filter(name="Visitor").count(),1,"There should be one UserLevel (=Visitor)")
29   -
30   - def test_login(self):
31   - self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
32   - self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
33   -
34   - # Activate user
35   - # La variable qui rรฉgit l'activation d'un compte est contenue dans pyrosUsers
36   - # et s'appelle is_active, il suffit de passer cette variable ร  True
37   - current_user = PyrosUser.objects.all()[0]
38   - current_user.is_active=True
39   - current_user.save()
40   - self.assertEqual(PyrosUser.objects.all()[0].is_active, True, "user should be active")
41   -
42   - # Log user
43   - path = "/user_manager/login"
44   - response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze"})
45   - self.assertTrue(response.context.get("success"))
46   - self.assertIn('_auth_user_id', self.client.session, "The user should be logged in")
47   -
48   - def test_login_not_active(self):
49   - self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
50   - self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
51   -
52   - # Activate user
53   - # La variable qui rรฉgit l'activation d'un compte est contenue dans pyrosUsers
54   - # et s'appelle is_active, il suffit de passer cette variable ร  True
55   -
56   - self.assertEqual(PyrosUser.objects.all()[0].is_active, False, "user should not be active")
57   -
58   - # Log user
59   - path = "/user_manager/login"
60   - response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze"})
61   -
62   - self.assertFalse(response.context.get("success"))
63   - self.assertNotIn('_auth_user_id', self.client.session, "The user should be logged in")
64   -
65   - def test_wrong_email(self):
66   - path = "/user_manager/login"
67   - response = self.client.post(path, {"email": "toto@tti.fr", "password": "aze"})
68   - self.assertIn("error", response.context.keys(), "There should be an error")
69   - self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
70   -
71   - def test_wrong_password(self):
72   - path = "/user_manager/login"
73   - response = self.client.post(path, {"email": "toto@titi.fr", "password": "azee"})
74   - self.assertIn("error", response.context.keys(), "There should be an error")
75   - self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
76   -
77   - def test_logout(self):
78   - self.client.login(username="toto@titi.fr", password="aze")
79   - path = "/user_manager/logout"
80   - self.client.get(path)
81   - self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
82   -
83   - def test_get_user_priority(self):
84   - user = PyrosUser.objects.all()[0]
85   - # add PI role to user
86   - UserLevel.objects.get(name="PI").pyros_users.add(user)
87   - self.assertEqual(user.get_priority(),UserLevel.objects.get(name="PI").priority,"The priority should be equal to PI's priority")
88   -
89   - def test_wrong_get_user_priority(self):
90   - user = PyrosUser.objects.all()[0]
91   - # add PI role to user
92   - UserLevel.objects.get(name="PI").pyros_users.add(user)
93   - self.assertNotEqual(user.get_priority(),UserLevel.objects.get(name="Visitor").priority,"The priority shouldn't be equal to Visitor's priority")
94   -
95   - def test_get_roles_str(self):
96   - user = PyrosUser.objects.all()[0]
97   - # add PI role to user
98   - UserLevel.objects.get(name="PI").pyros_users.add(user)
99   - roles_str = user.get_roles_str()
100   - for role in user.user_level.all():
101   - self.assertIn(role.name,roles_str,f"The role {role} should be in the str representation")
102   -
103   - def test_wrong_get_roles_str(self):
104   - user = PyrosUser.objects.all()[0]
105   - # add PI role to user
106   - UserLevel.objects.get(name="PI").pyros_users.add(user)
107   - roles_str = user.get_roles_str()
108   - self.assertNotIn(UserLevel.objects.get(name="SysAdmin").name,roles_str,"The role SysAdmin shouldn't be in the str representation")
109   -
110   - def test_max_priority_desc(self):
111   - user = PyrosUser.objects.all()[0]
112   - # add PI role to user
113   - UserLevel.objects.get(name="PI").pyros_users.add(user)
114   - self.assertEqual(user.get_max_priority_desc(),UserLevel.objects.get(name="PI").desc,"The desc of user_level should be 'PI description' ")
115   -
116   - def test_wrong_max_priority_desc(self):
117   - user = PyrosUser.objects.all()[0]
118   - # add PI role to user
119   - UserLevel.objects.get(name="PI").pyros_users.add(user)
120   - # add SysAdmin role to user (has a greatier priority than PI and Visitor roles)
121   - UserLevel.objects.get(name="SysAdmin").pyros_users.add(user)
122   - self.assertNotEqual(user.get_max_priority_desc(),UserLevel.objects.get(name="PI").desc,"The desc of user_level shouldn't be 'PI description' ")
123   -
124   -
125   - def test_max_priority_quota(self):
126   - user = PyrosUser.objects.all()[0]
127   - # add PI role to user
128   - UserLevel.objects.get(name="PI").pyros_users.add(user)
129   - self.assertEqual(user.get_max_priority_quota(),UserLevel.objects.get(name="PI").quota,"The quota of user_level should be 100.0 ")
130   -
131   - def test_wrong_max_priority_quota(self):
132   - user = PyrosUser.objects.all()[0]
133   - # add PI role to user
134   - UserLevel.objects.get(name="PI").pyros_users.add(user)
135   - # add SysAdmin role to user (has a greatier priority than PI and Visitor roles)
136   - UserLevel.objects.get(name="SysAdmin").pyros_users.add(user)
137   - self.assertNotEqual(user.get_max_priority_quota(),UserLevel.objects.get(name="PI").desc,"The quota of user_level shouldn't be 1000.0 ")
138 1 \ No newline at end of file
  2 +from django.test import TestCase
  3 +from common.models import *
  4 +from django.contrib.auth.models import User
  5 +from django.urls import reverse
  6 +
  7 +
  8 +
  9 +class UserManagerTests(TestCase):
  10 +
  11 + def setUp(self):
  12 + institute = Institute.objects.create(name="CNRS",quota=999.0)
  13 + UserLevel.objects.create(name="Visitor",desc="Visitor description",priority=0,quota=0.0)
  14 + UserLevel.objects.create(name="Unit-PI",desc="Unit-PI description",priority=5,quota=100.0)
  15 + UserLevel.objects.create(name="Admin",desc="Admin description",priority=7,quota=1000.0)
  16 + Country.objects.create()
  17 + self.assertEqual(PyrosUser.objects.count(), 0, "There should be no User")
  18 + path = reverse("user_signup_validation")
  19 + response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze", "password_confirm": "aze",
  20 + "first_name": "toto", "last_name": "titi", "tel": "0123456789",
  21 + "laboratory": "IRAP", "address": "ici","institute":institute.id,"reason":"this is a test",
  22 + "roles":"Admin"
  23 + })
  24 +
  25 +
  26 + self.assertTrue("success" in response.context.keys(), "There should be a success")
  27 + self.assertEqual(PyrosUser.objects.count(), 1, "Theroue shld be one User")
  28 +
  29 + def test_creation(self):
  30 + self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
  31 + self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
  32 +
  33 + self.assertEqual(PyrosUser.objects.all()[0].first_name, 'toto')
  34 + self.assertEqual(PyrosUser.objects.all()[0].email, 'toto@titi.fr')
  35 + self.assertEqual(PyrosUser.objects.all()[0].user_level.filter(name="Visitor").count(),1,"There should be one UserLevel (=Visitor)")
  36 +
  37 + def test_login(self):
  38 + self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
  39 + self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
  40 +
  41 + # Activate user
  42 + # La variable qui rรฉgit l'activation d'un compte est contenue dans pyrosUsers
  43 + # et s'appelle is_active, il suffit de passer cette variable ร  True
  44 + current_user = PyrosUser.objects.all()[0]
  45 + current_user.is_active=True
  46 + current_user.save()
  47 + self.assertEqual(PyrosUser.objects.all()[0].is_active, True, "user should be active")
  48 +
  49 + # Log user
  50 + path = "/user_manager/login"
  51 + response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze"})
  52 + self.assertTrue(response.context.get("success"))
  53 + self.assertIn('_auth_user_id', self.client.session, "The user should be logged in")
  54 +
  55 + def test_login_not_active(self):
  56 + self.assertEqual(Country.objects.count(), 1, "There should be 1 Country")
  57 + self.assertEqual(UserLevel.objects.count(), 3, "There should be 3 UserLevel")
  58 +
  59 + # Activate user
  60 + # La variable qui rรฉgit l'activation d'un compte est contenue dans pyrosUsers
  61 + # et s'appelle is_active, il suffit de passer cette variable ร  True
  62 +
  63 + self.assertEqual(PyrosUser.objects.all()[0].is_active, False, "user should not be active")
  64 +
  65 + # Log user
  66 + path = "/user_manager/login"
  67 + response = self.client.post(path, {"email": "toto@titi.fr", "password": "aze"})
  68 +
  69 + self.assertFalse(response.context.get("success"))
  70 + self.assertNotIn('_auth_user_id', self.client.session, "The user should be logged in")
  71 +
  72 + def test_wrong_email(self):
  73 + path = "/user_manager/login"
  74 + response = self.client.post(path, {"email": "toto@tti.fr", "password": "aze"})
  75 + self.assertIn("error", response.context.keys(), "There should be an error")
  76 + self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
  77 +
  78 + def test_wrong_password(self):
  79 + path = "/user_manager/login"
  80 + response = self.client.post(path, {"email": "toto@titi.fr", "password": "azee"})
  81 + self.assertIn("error", response.context.keys(), "There should be an error")
  82 + self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
  83 +
  84 + def test_logout(self):
  85 + self.client.login(username="toto@titi.fr", password="aze")
  86 + path = "/user_manager/logout"
  87 + self.client.get(path)
  88 + self.assertNotIn('_auth_user_id', self.client.session, "There shouldn't be an authentified user")
  89 +
  90 + def test_get_user_priority(self):
  91 + user = PyrosUser.objects.all()[0]
  92 + # add Unit-PI role to user
  93 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  94 + self.assertEqual(user.get_priority(),UserLevel.objects.get(name="Unit-PI").priority,"The priority should be equal to Unit-PI's priority")
  95 +
  96 + def test_wrong_get_user_priority(self):
  97 + user = PyrosUser.objects.all()[0]
  98 + # add Unit-PI role to user
  99 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  100 + self.assertNotEqual(user.get_priority(),UserLevel.objects.get(name="Visitor").priority,"The priority shouldn't be equal to Visitor's priority")
  101 +
  102 + def test_get_roles_str(self):
  103 + user = PyrosUser.objects.all()[0]
  104 + # add Unit-PI role to user
  105 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  106 + roles_str = user.get_roles_str()
  107 + for role in user.user_level.all():
  108 + self.assertIn(role.name,roles_str,f"The role {role} should be in the str representation")
  109 +
  110 + def test_wrong_get_roles_str(self):
  111 + user = PyrosUser.objects.all()[0]
  112 + # add Unit-PI role to user
  113 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  114 + roles_str = user.get_roles_str()
  115 + self.assertNotIn(UserLevel.objects.get(name="Admin").name,roles_str,"The role Admin shouldn't be in the str representation")
  116 +
  117 + def test_max_priority_desc(self):
  118 + user = PyrosUser.objects.all()[0]
  119 + # add Unit-PI role to user
  120 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  121 + self.assertEqual(user.get_max_priority_desc(),UserLevel.objects.get(name="Unit-PI").desc,"The desc of user_level should be 'Unit-PI description' ")
  122 +
  123 + def test_wrong_max_priority_desc(self):
  124 + user = PyrosUser.objects.all()[0]
  125 + # add Unit-PI role to user
  126 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  127 + # add Admin role to user (has a greatier priority than Unit-PI and Visitor roles)
  128 + UserLevel.objects.get(name="Admin").pyros_users.add(user)
  129 + self.assertNotEqual(user.get_max_priority_desc(),UserLevel.objects.get(name="Unit-PI").desc,"The desc of user_level shouldn't be 'Unit-PI description' ")
  130 +
  131 +
  132 + def test_max_priority_quota(self):
  133 + user = PyrosUser.objects.all()[0]
  134 + # add Unit-PI role to user
  135 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  136 + self.assertEqual(user.get_max_priority_quota(),UserLevel.objects.get(name="Unit-PI").quota,"The quota of user_level should be 100.0 ")
  137 +
  138 + def test_wrong_max_priority_quota(self):
  139 + user = PyrosUser.objects.all()[0]
  140 + # add Unit-PI role to user
  141 + UserLevel.objects.get(name="Unit-PI").pyros_users.add(user)
  142 + # add Admin role to user (has a greatier priority than Unit-PI and Visitor roles)
  143 + UserLevel.objects.get(name="Admin").pyros_users.add(user)
  144 + self.assertNotEqual(user.get_max_priority_quota(),UserLevel.objects.get(name="Unit-PI").desc,"The quota of user_level shouldn't be 1000.0 ")
139 145 \ No newline at end of file
... ...
src/core/pyros_django/user_manager/urls.py
1   -from django.conf.urls import url
2   -from django.urls import path
3   -from . import views
4   -
5   -urlpatterns = [
6   - url(r'^create$', views.create_user, name="create_user"),
7   - url(r'^creation_validate$', views.user_signup_validation, name="user_signup_validation"),
8   - url(r'^login$', views.login_validation, name="login_validation"),
9   - url(r'^profile$', views.profile, name="profile"),
10   - url(r'^logout$', views.user_logout, name="user_logout"),
11   - path('signin', views.user_signin, name="user_signin"),
12   - path('sp_profile', views.superoperator_return, name="sp_profile")
13   - ]
  1 +from django.conf.urls import url
  2 +from django.urls import path
  3 +from . import views
  4 +from django.contrib.auth import views as auth_views
  5 +from .forms import UserPasswordResetForm
  6 +
  7 +urlpatterns = [
  8 + path('users', views.users, name="users"),
  9 + url(r'^create$', views.create_user, name="create_user"),
  10 + url(r'^creation_validate$', views.user_signup_validation, name="user_signup_validation"),
  11 + url(r'^login$', views.login_validation, name="login_validation"),
  12 + url(r'^profile$', views.profile, name="profile"),
  13 + url(r'^logout$', views.user_logout, name="user_logout"),
  14 + path('signin', views.user_signin, name="user_signin"),
  15 + path('sp_profile', views.superoperator_return, name="sp_profile"),
  16 + path('users/<int:pk>', views.user_detail_view, name='user_detail'),
  17 + path('users_delete/<int:pk>', views.delete_user, name='user-delete'),
  18 + path('users/<int:pk>/edit', views.user_detail_edit, name='user-edit'),
  19 + path('user-status/<int:pk>/<int:current_user_id>', views.change_activate, name='change_activate'),
  20 + path("set_active_role", views.set_active_role,name="set_active_role"),
  21 + path("password-reset", auth_views.PasswordResetView.as_view( template_name="user_manager/password_reset.html"), name="password_reset"),
  22 + path("password-reset/done/", auth_views.PasswordResetDoneView.as_view( template_name="user_manager/password_reset_done.html"), name="password_reset_done"),
  23 + path("password-reset-confirm/<uidb64>/<token>", auth_views.PasswordResetConfirmView.as_view( template_name="user_manager/password_reset_confirm.html"), name="password_reset_confirm"),
  24 + path("password-reset-complete/", auth_views.PasswordResetCompleteView.as_view( template_name="user_manager/password_reset_complete.html"), name="password_reset_complete"),
  25 + path('roles_description', views.roles_description, name="roles_description")
  26 + ]
... ...
src/core/pyros_django/user_manager/views.py
1   -from django.shortcuts import render,redirect
2   -from django.contrib.auth import authenticate, login, logout
3   -from django.contrib.auth.decorators import login_required
4   -from .forms import PyrosUserCreationForm
5   -
6   -LOGGED_PAGE = "../../dashboard/templates/dashboard/index.html"
7   -
8   -def home(request):
9   - '''
10   - Initial login view when coming on the website
11   - '''
12   - if request.user.is_authenticated:
13   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
14   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': 0, 'base_template' : 'base_unlogged.html', 'weather_img': "red"}))
15   -
16   -def create_user(request):
17   - '''
18   - View called to open the user creation form
19   - '''
20   - if request.user.is_authenticated:
21   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
22   - form = PyrosUserCreationForm()
23   - return (render(request, "user_manager/home_user_creation.html", locals()))
24   -
25   -def user_signup_validation(request):
26   - '''
27   - View called to validate the user creation (form submitted)
28   - '''
29   - if request.user.is_authenticated:
30   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
31   - form = PyrosUserCreationForm(request.POST)
32   - if request.POST:
33   - if form.is_valid():
34   - form.save()
35   - message = "Account creation successful ! Login to continue"
36   - success = True
37   - return(render(request, "user_manager/home_login.html", locals()))
38   - else:
39   - message = "One or more fields contain errors. Please try again"
40   -
41   - else:
42   - message = "The system encountered an error. Please try again"
43   -
44   - error = True
45   - return (render(request, "user_manager/home_user_creation.html", locals()))
46   -
47   -def login_validation(request):
48   - '''
49   - View called when the user log in (form submitted)
50   - '''
51   - if request.user.is_authenticated:
52   - if request.POST.get("next"):
53   - return redirect(request.POST.get('next'))
54   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(),'base_template' : "base.html", 'weather_img': "normal"}))
55   - username = password = ''
56   - if request.POST:
57   - email = request.POST.get('email')
58   - password = request.POST.get('password')
59   - user = authenticate(username=email, password=password)
60   - if user is not None:
61   - success = False
62   - if user.is_active:
63   - login(request, user)
64   - request.session['user'] = email
65   - message = "Oui"
66   - success = True
67   - if request.POST.get("next"):
68   - return redirect(request.POST.get('next'))
69   - return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal", 'success' : success}))
70   - else:
71   - message = "Your account is not active, please contact the site administrator."
72   - else:
73   - message = "Your email and/or password were incorrect."
74   - else:
75   - message = "An unexpected error has occurred"
76   - error = True
77   - return(render(request, "user_manager/home_login.html", locals()))
78   -
79   -@login_required
80   -def profile(request):
81   - '''
82   - View called to see the current user profile
83   - '''
84   - current_user = request.user
85   - USER_LEVEL = request.user.get_priority()
86   - if (current_user.get_priority() < 4):
87   - return(render(request, "dashboard/user_detail.html", {'user': current_user, 'admin': 0}))
88   - return(render(request, "user_manager/profile.html", locals()))
89   -
90   -@login_required
91   -def superoperator_return(request):
92   - current_user = request.user
93   - return(render(request, "dashboard/user_detail.html", {'user': current_user, 'admin': 0}))
94   -
95   -@login_required
96   -def user_logout(request):
97   - '''
98   - View called to log out. Redirects on login page.
99   - '''
100   -
101   - logout(request)
102   - return(render(request, LOGGED_PAGE, {'USER_LEVEL' : 0, 'base_template' : 'base_unlogged.html', 'weather_img': "red"}))
103   -
104   -def user_signin(request):
105   - return(render(request, "user_manager/home_login.html",{"next": request.GET.get("next")}))
106 1 \ No newline at end of file
  2 +from django.shortcuts import render,redirect
  3 +from django.contrib.auth import authenticate, login, logout
  4 +from django.contrib.auth.decorators import login_required
  5 +from dashboard.decorator import level_required
  6 +from django.shortcuts import get_object_or_404
  7 +from dashboard.forms import UserForm
  8 +from .forms import PyrosUserCreationForm
  9 +from django.core.mail import send_mail
  10 +from common.models import ScientificProgram, PyrosUser,UserLevel, SP_Period, SP_Period_User
  11 +from django.urls import reverse
  12 +from django.http import HttpResponseRedirect,HttpResponse
  13 +
  14 +
  15 +LOGGED_PAGE = "../../dashboard/templates/dashboard/index.html"
  16 +
  17 +def home(request):
  18 + '''
  19 + Initial login view when coming on the website
  20 + '''
  21 + if request.user.is_authenticated:
  22 + return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
  23 + return(render(request, LOGGED_PAGE, {"USER_LEVEL" : "Visitor", 'base_template' : 'base_unlogged.html', 'weather_img': "red"}))
  24 +
  25 +def roles_description(request):
  26 + return (render(request,"user_manager/roles_description.html"))
  27 +
  28 +def create_user(request):
  29 + '''
  30 + View called to open the user creation form
  31 + '''
  32 + """
  33 + if request.user.is_authenticated:
  34 + return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
  35 + """
  36 + form = PyrosUserCreationForm()
  37 + return (render(request, "user_manager/home_user_creation.html", locals()))
  38 +
  39 +def user_signup_validation(request):
  40 + '''
  41 + View called to validate the user creation (form submitted)
  42 + '''
  43 + """
  44 + if request.user.is_authenticated:
  45 + return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal"}))
  46 + """
  47 + form = PyrosUserCreationForm(request.POST)
  48 + if request.POST:
  49 + if form.is_valid():
  50 + form.save()
  51 + message = "Account creation successful ! Login to continue"
  52 + success = True
  53 + if request.user.is_authenticated:
  54 + if request.POST.get("next"):
  55 + return redirect(request.POST.get('next'))
  56 + else:
  57 + return redirect(reverse("users"))
  58 + else:
  59 + return(render(request, "user_manager/home_login.html", locals()))
  60 + else:
  61 + message = "One or more fields contain errors. Please try again"
  62 + form_errors = form.errors
  63 + else:
  64 + message = "The system encountered an error. Please try again"
  65 +
  66 + error = True
  67 + return (render(request, "user_manager/home_user_creation.html", locals()))
  68 +
  69 +def login_validation(request):
  70 + '''
  71 + View called when the user log in (form submitted)
  72 + '''
  73 + if request.user.is_authenticated:
  74 + if request.POST.get("next"):
  75 + return redirect(request.POST.get('next'))
  76 + # initiate variable session for telling which role the user is using if this user has multiple roles
  77 + # default role is the role with maximum priority
  78 + request.session["role"] = str(UserLevel.objects.get(priority=request.user.get_priority()))
  79 + return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(),'base_template' : "base.html", 'weather_img': "normal"}))
  80 + username = password = ''
  81 + if request.POST:
  82 + email = request.POST.get('email')
  83 + password = request.POST.get('password')
  84 + user = authenticate(username=email, password=password)
  85 + if user is not None:
  86 + success = False
  87 + if user.is_active:
  88 + login(request, user)
  89 + request.session['user'] = email
  90 + message = "Oui"
  91 + success = True
  92 + # initiate variable session for telling which role the user is using if this user has multiple roles
  93 + # default role is the role with maximum priority
  94 + request.session["role"] = str(UserLevel.objects.get(priority=request.user.get_priority()))
  95 + if request.POST.get("next"):
  96 + return redirect(request.POST.get('next'))
  97 + return(render(request, LOGGED_PAGE, {'USER_LEVEL': request.user.get_priority(), 'base_template' : "base.html", 'weather_img': "normal", 'success' : success}))
  98 + else:
  99 + message = "Your account is not active, please contact the site administrator."
  100 + else:
  101 + message = "Your email and/or password were incorrect."
  102 + else:
  103 + message = "An unexpected error has occurred"
  104 + error = True
  105 + return(render(request, "user_manager/home_login.html", locals()))
  106 +
  107 +@login_required
  108 +def profile(request):
  109 + '''
  110 + View called to see the current user profile
  111 + '''
  112 + current_user = request.user
  113 + USER_LEVEL = request.user.get_priority()
  114 + if (current_user.get_priority() < 4):
  115 + return(render(request, "user_manager/user_detail.html", {'user': current_user, 'admin': 0}))
  116 + return(render(request, "user_manager/profile.html", locals()))
  117 +
  118 +@login_required
  119 +def superoperator_return(request):
  120 + current_user = request.user
  121 + return(render(request, "user_manager/user_detail.html", {'user': current_user, 'admin': 0}))
  122 +
  123 +@login_required
  124 +def user_logout(request):
  125 + '''
  126 + View called to log out. Redirects on login page.
  127 + '''
  128 +
  129 + logout(request)
  130 + return(render(request, LOGGED_PAGE, {'USER_LEVEL' : "Visitor", 'base_template' : 'base_unlogged.html', 'weather_img': "red"}))
  131 +
  132 +def user_signin(request):
  133 + return(render(request, "user_manager/home_login.html",{"next": request.GET.get("next")}))
  134 +
  135 +
  136 +@login_required
  137 +@level_required("Admin")
  138 +def delete_user(request,pk):
  139 + user_to_be_deleted = get_object_or_404(PyrosUser,pk=pk)
  140 + if request.method == "POST":
  141 + user_to_be_deleted.delete()
  142 + return HttpResponseRedirect(reverse('users'))
  143 +
  144 +
  145 +@login_required
  146 +@level_required("Admin","Observer","Management","Operator","Unit-PI","TAC","Unit board")
  147 +def users(request):
  148 + current_user = request.user
  149 + pyros_users_with_roles = []
  150 + pyros_users_without_roles = None
  151 + if request.session.get("role"):
  152 + role = request.session.get("role")
  153 + else:
  154 + role = current_user.get_priority()
  155 +
  156 + if role in "Admin,Unit-PI,Unit board":
  157 + pyros_users_with_roles = PyrosUser.objects.exclude(user_level__name="Visitor").order_by("-id")
  158 + pyros_users_without_roles = PyrosUser.objects.filter(user_level__name="Visitor").order_by("-id")
  159 + else:
  160 + sp_of_current_user = SP_Period_User.objects.filter(user=current_user)
  161 + pyros_user_with_roles = []
  162 + for sp in sp_of_current_user:
  163 + for user in SP_Period_User.objects.filter(SP_Period=sp.SP_Period).exclude(user=current_user).values_list("user",flat=True):
  164 + pyros_users_with_roles.append(PyrosUser.objects.get(id=user))
  165 + nb_of_scientific_program = ScientificProgram.objects.count()
  166 + # need the negative to calculate in the template for adjusting correctly the information display
  167 + negative_nb_scientific_program = -nb_of_scientific_program
  168 + return render(request, 'user_manager/users_management.html', {'pyros_users_with_roles': pyros_users_with_roles,"pyros_users_without_roles":pyros_users_without_roles,"nb_of_scientific_program": nb_of_scientific_program,"negative_nb_scientific_program":negative_nb_scientific_program}) # return the initial view (the users management's one)
  169 +
  170 +@login_required
  171 +@level_required("Admin","Unit-PI","Unit board")
  172 +def change_activate(request, pk, current_user_id):
  173 + try :
  174 + user = get_object_or_404(PyrosUser, pk=pk)
  175 + user.is_active = not user.is_active
  176 + text_mail = ""
  177 + text_object = ""
  178 + if (user.first_time == False and user.is_active == True):
  179 + user.first_time = True
  180 + text_mail = "Hi,\n\nCongratulations, your registration has been approved by the PI. Welcome to the PyROS Control Center.\nIn order to submit observation sequences, you need to be associated to a scientific program.\n\nCordially,\n\nPyROS Control Center"
  181 + text_object = "[PyROS CC] Welcome"
  182 + user.validator = get_object_or_404(PyrosUser,pk=current_user_id)
  183 + send_mail(text_object, text_mail, '', [user.email], fail_silently=False,)
  184 +
  185 + # We're not sending an email if the account has been desactivated or re-activated
  186 + # elif (user.is_active == True):
  187 + # text_mail = "Hi,\n\nYour account on the PyROS Control Center have been re-activated.\n\nCordially,\n\nPyROS Control Center"
  188 + # text_object = "[PyROS CC] Re-activation"
  189 + # else :
  190 + # text_mail = "Hi,\n\nYour account on the PyROS Control Center have benn desactivated. Please contact the PI for futher information.\n\nCordially,\n\nPyROS Control Center"
  191 + # text_object = "[PyROS CC] Desactivation"
  192 +
  193 + user.save()
  194 +
  195 + return redirect('user_detail', pk=pk)
  196 + except PyrosUser.DoesNotExist:
  197 + return redirect('user_detail', pk=pk)
  198 +
  199 +@login_required
  200 +@level_required("Admin","Observer","Management","Operator","Unit-PI","TAC","Unit board")
  201 +def user_detail_view(request,pk):
  202 + try:
  203 + is_last_user = PyrosUser.objects.count() == 1
  204 + user_id=PyrosUser.objects.get(pk=pk)
  205 + current_user = request.user
  206 + roles = current_user.get_list_of_roles()
  207 + sp_periods = SP_Period_User.objects.filter(user=user_id)
  208 + scientific_programs = []
  209 + for sp_period in sp_periods:
  210 +
  211 + scientific_programs.append(sp_period.SP_Period.scientific_program)
  212 + except PyrosUser.DoesNotExist:
  213 + raise Http404("User does not exist")
  214 + return render(request, 'user_manager/user_detail.html', context={'user' : user_id, 'current_user' : current_user, 'USER_LEVEL': request.user.get_priority(), 'is_last_user' : is_last_user,"roles" : roles,"scientific_programs":scientific_programs})
  215 +
  216 +@login_required
  217 +@level_required()
  218 +def user_detail_edit(request,pk):
  219 + if request.session.get("role"):
  220 + role = request.session.get("role")
  221 + else:
  222 + role = request.user.get_priority()
  223 + # If its not his user profile or user isn't Unit-PI, Unit board, Admin or SP-PI, He can't edit this user profile and he is redirected to home page
  224 + if (request.user.id != pk and role not in ("Admin","Unit-PI","Unit board") ):
  225 + return HttpResponseRedirect(reverse('index'))
  226 + edit = get_object_or_404(PyrosUser, pk=pk)
  227 + is_sp_pi = SP_Period_User.objects.filter(is_SP_PI=True,user=edit).count() > 0
  228 + form = UserForm(request.POST or None, instance=edit)
  229 + # creating list of roles for the formular excluding visitor of the list
  230 + roles = UserLevel.objects.exclude(name="Visitor")
  231 + if form.is_valid():
  232 + obj = form.save(commit=False)
  233 + if(len(request.POST.getlist("roles"))>0):
  234 + if("Admin" in request.POST.getlist("roles")):
  235 + # if Admin role has been assigned, add the authorisations to access to django admin pages
  236 + obj.is_staff = True
  237 + obj.is_admin = True
  238 + obj.is_superuser = True
  239 + else:
  240 + # just in case (for example, if user was previously an admin and has been downgraded) we're removing those authorisations
  241 + obj.is_staff = False
  242 + obj.is_admin = False
  243 + obj.user_level.set(request.POST.getlist("roles"))
  244 + else:
  245 + # No role has been assigned, so the user has the Visitor role
  246 + obj.user_level.set([UserLevel.objects.get(name="Visitor")])
  247 +
  248 + obj.save()
  249 + return redirect('user_detail', pk=pk)
  250 + return render(request, 'user_manager/user_detail_edit.html', {'form': form,"roles":roles, "pk":pk,"user_edit":edit,'USER_LEVEL': request.user.get_priority(),"is_sp_pi":is_sp_pi})
  251 +
  252 +
  253 +def set_active_role(request):
  254 + previous_active_role = request.session.get("role")
  255 + if request.user.is_authenticated:
  256 + if request.POST.get("role"):
  257 + request.session["role"] = str(UserLevel.objects.get(name=request.POST.get("role")))
  258 + if(previous_active_role is not None and previous_active_role != request.session.get("role")):
  259 + return HttpResponse("Changed !")
107 260 \ No newline at end of file
... ...