Commit 3fbfccbd authored by Giorgos Kazelidis's avatar Giorgos Kazelidis

- Implemented the user credentials import task

- Implemented the user report creation task
parent d8ab8257
import re import re
from django import forms from django import forms
from .validators import ece_id_is_not_031YY000, platform_is_SLUB_or_exists_in_DB from .validators import (
ece_id_is_not_031YY000, last_login_is_selected_from_provided_list, platform_exists_in_DB, platform_is_SLUB_or_exists_in_DB
)
# Create your forms here. # Create your forms here.
# https://docs.djangoproject.com/en/2.0/ref/forms/ # https://docs.djangoproject.com/en/2.0/ref/forms/
...@@ -43,3 +45,40 @@ class UserProfileRecoveryForm(UserProfileEditForm): ...@@ -43,3 +45,40 @@ class UserProfileRecoveryForm(UserProfileEditForm):
first_name = None first_name = None
last_name = None last_name = None
class UserCredentialsImportForm(forms.Form):
platform = forms.CharField(error_messages = {'required': 'Το όνομα πλατφόρμας πρέπει απαραίτητα να επιλεχθεί και να είναι μη-κενό!'},
validators = [platform_exists_in_DB])
credentials_file = forms.FileField(max_length = 50,
error_messages = {'required': 'Το αρχείο διαπιστευτηρίων πρέπει απαραίτητα να υποβληθεί και να '
'έχει μη-κενό όνομα!',
'invalid': 'Η υποβολή του αρχείου διαπιστευτηρίων απέτυχε! Παρακαλούμε '
'ελέγξτε την ορθότητα του\n'
'τύπου κωδικοποίησης στη φόρμα προσθήκης διαπιστευτηρίων '
'και δοκιμάστε ξανά!',
'missing': 'Η υποβολή του αρχείου διαπιστευτηρίων απέτυχε!',
'max_length': 'Το όνομα του αρχείου διαπιστευτηρίων δεν πρέπει '
'να υπερβαίνει τους 50 χαρακτήρες!',
'empty': 'Το περιεχόμενο του αρχείου διαπιστευτηρίων δεν πρέπει να είναι κενό!'})
def clean_credentials_file(self):
credentials_file = self.cleaned_data['credentials_file']
# Ensure that the credentials file is a plain text file whose size does not exceed 40KB.
# Otherwise, close it and raise a ValidationError exception with the appropriate error message and code.
if credentials_file.content_type != 'text/plain':
credentials_file.close()
raise forms.ValidationError('Το αρχείο διαπιστευτηρίων πρέπει να είναι ένα απλό αρχείο κειμένου!',
code = 'credentials_file_content_type_is_not_plain_text')
elif credentials_file.size > 1024 * 40:
credentials_file.close()
raise forms.ValidationError('Το μέγεθος του αρχείου διαπιστευτηρίων δεν πρέπει να υπερβαίνει τα 40KB!',
code = 'credentials_file_size_exceeds_40KB')
else:
return credentials_file
class UserReportCreationForm(forms.Form):
platform = forms.CharField(error_messages = {'required': 'Το όνομα πλατφόρμας πρέπει απαραίτητα να επιλεχθεί και να είναι μη-κενό!'},
validators = [platform_exists_in_DB])
last_login = forms.CharField(error_messages = {'required': 'Η περίοδος αναζήτησης τελευταίας εισόδου πρέπει απαραίτητα να επιλεχθεί '
'και να είναι μη-κενή!'},
validators = [last_login_is_selected_from_provided_list])
...@@ -32,6 +32,23 @@ def get_form_error_messages(form): ...@@ -32,6 +32,23 @@ def get_form_error_messages(form):
raise TypeError('The form argument of get_form_error_messages() helper function ' raise TypeError('The form argument of get_form_error_messages() helper function '
'should be an instance of "django.forms.forms.Form" class!') 'should be an instance of "django.forms.forms.Form" class!')
def seq_to_aligned_str(seq, max_line_len):
"""
Create an aligned string from the elements of the given sequence. This string separates every max_line_len elements (along with the
delimiting commas) by a newline character without changing their order (the last line may contain less than max_line_len elements).
If max_line_len is a non-positive number, raise a ValueError exception with the appropriate error message.
"""
if max_line_len <= 0:
raise ValueError('The max_line_len argument of seq_to_aligned_str() helper function should be a positive integer!')
seq = list(seq) # Turn seq into a list to ensure that it has the appropriate sequence format.
aligned_str = ''
for idx in range(0, len(seq), max_line_len):
if idx + max_line_len < len(seq):
aligned_str += str(seq[idx:idx + max_line_len])[1:-1] + ',\n'
else:
aligned_str += str(seq[idx:len(seq)])[1:-1]
return aligned_str
def _update_user_profile(request, changed_data, cleaned_data): def _update_user_profile(request, changed_data, cleaned_data):
""" """
[Acts as inner function of the edit_user_profile() view] Check which fields of the user profile edit form have been changed, update [Acts as inner function of the edit_user_profile() view] Check which fields of the user profile edit form have been changed, update
......
...@@ -13,11 +13,11 @@ from django.utils.timezone import localtime ...@@ -13,11 +13,11 @@ from django.utils.timezone import localtime
# The id and last_login fields are populated/updated automatically (each model is implicitly given an id AutoField that acts as the corresponding primary key, each last_login field is initially empty and is updated whenever the corresponding user_logged_in signal is emitted - see the relative explanation on last_login fields below) and should NOT be declared during the creation/update of model instances. The REMAINING fields should satisfy specific requirements (see examples in populateDB.py): # The id and last_login fields are populated/updated automatically (each model is implicitly given an id AutoField that acts as the corresponding primary key, each last_login field is initially empty and is updated whenever the corresponding user_logged_in signal is emitted - see the relative explanation on last_login fields below) and should NOT be declared during the creation/update of model instances. The REMAINING fields should satisfy specific requirements (see examples in populateDB.py):
# * The name field of a Platform instance should NEVER be empty. Its value can be changed validly/consistently but it should NEVER become empty. A Platform instance can be created/changed in usermergeDB ONLY in a NON-session context, e.g. fixture - see https://docs.djangoproject.com/en/2.0/howto/initial-data/, interactive shell - see https://docs.djangoproject.com/en/2.0/ref/django-admin/, file such as populateDB.py, etc. # * The name field of a Platform instance should NEVER be empty. Its value can be changed validly/consistently but it should NEVER become empty. A Platform instance can be created/changed in usermergeDB ONLY in a NON-session context, e.g. fixture - see https://docs.djangoproject.com/en/2.0/howto/initial-data/, interactive shell - see https://docs.djangoproject.com/en/2.0/ref/django-admin/, file such as populateDB.py, etc.
# * ALL the fields of a Registry instance should be NON-empty. The user (user_id), username and password values can be changed validly/consistently (the platform - platform_id - value should NEVER be changed) but they should NEVER become empty. When a Registry instance is initially created, an empty User instance should also be created and associated with that Registry instance - see the below explanation about the User instance fields. # * ALL the fields of a Registry instance should be NON-empty. The user (user_id), username and password values can be changed validly/consistently (the platform - platform_id - value should NEVER be changed) but they should NEVER become empty. When a Registry instance is initially created, an empty User instance should also be created and associated with that Registry instance - see the import_user_credentials() view.
# * ALL the fields of an Admin instance should be NON-empty. Their values can be changed validly/consistently but they should NEVER become empty. An Admin instance can be created/changed in usermergeDB ONLY in a NON-session context, e.g. fixture, interactive shell, file such as populateDB.py, etc. (see the relative references above). # * ALL the fields of an Admin instance should be NON-empty. Their values can be changed validly/consistently but they should NEVER become empty. An Admin instance can be created/changed in usermergeDB ONLY in a NON-session context, e.g. fixture, interactive shell, file such as populateDB.py, etc. (see the relative references above).
# * The fields of a User instance should ALWAYS be in ONE of the following states: 1)ALL filled out, 2)ALL filled out EXCEPT FOR ece_id, 3)NONE filled out. The allowed transitions between field states are: 1-->1, 2-->1, 2-->2, 3-->1 and 3-->2 (empty field values can be changed to valid/consistent non-empty ones, non-empty field values can be changed validly/consistently but they should NEVER become empty). When a User instance is initially created, ALL its fields should be empty (state 3) and EXACTLY ONE Registry instance should be created to reference it - see the above explanation about the Registry instance fields. The fields can then be changed/filled out (state 1/2) ONLY by the corresponding user via the user profile edit form after he/she has logged in first - see the edit_user_profile() view (any other way of changing/filling out the fields is by NO means accepted). # * The fields of a User instance should ALWAYS be in ONE of the following states: 1)ALL filled out, 2)ALL filled out EXCEPT FOR ece_id, 3)NONE filled out. The allowed transitions between field states are: 1-->1, 2-->1, 2-->2, 3-->1 and 3-->2 (empty field values can be changed to valid/consistent non-empty ones, non-empty field values can be changed validly/consistently but they should NEVER become empty). When a User instance is initially created, ALL its fields should be empty (state 3) and EXACTLY ONE Registry instance should be created to reference it - see the import_user_credentials() view. The fields can then be changed/filled out (state 1/2) ONLY by the corresponding user via the user profile edit form after he/she has logged in first - see the edit_user_profile() view (any other way of changing/filling out the fields is by NO means accepted).
# A User instance retains ONE last_login value FOR EACH Platform instance that is associated with it via the corresponding Registry instance, while an Admin instance is associated ONLY with the SLUB "platform", i.e. it is NOT associated with any Platform instances, and thus retains EXACTLY ONE last_login value. Each time a user logs in, an appropriate user_logged_in signal is emitted - see the login() function in auth.py - causing the last_login field of the corresponding User/Admin instance for the login platform (SLUB in case of an Admin instance) to be updated INDEPENDENTLY of its other fields. A NON-empty User instance should ALWAYS retain a NON-empty last_login value FOR EACH of its associated Platform instances - see the edit_user_profile() and recover_user_profile() views. On the other hand, an empty User instance generally retains an empty last_login value for its ONLY associated Platform instance but can also retain a NON-empty one for it (the user logs in and, although his/her profile is empty, he/she opts to log out - or is logged out automatically due to session expiration - WITHOUT filling it out). An Admin instance generally retains a NON-empty last_login value for the SLUB "platform" as it should be logged in to access any Admin-targeted functionality/view, but can also retain an empty one for it (the user has NEVER logged in but his/her profile has ALREADY been filled out in a NON-session context, e.g. fixture, interactive shell, file such as populateDB.py, etc. - see the relative references above). # A User instance retains ONE last_login value FOR EACH Platform instance that is associated with it via the corresponding Registry instance, while an Admin instance is associated ONLY with the SLUB "platform", i.e. it is NOT associated with any Platform instances, and thus retains EXACTLY ONE last_login value. Each time a user logs in, an appropriate user_logged_in signal is emitted - see the login() function in auth.py - causing the last_login field of the corresponding User/Admin instance for the login platform (SLUB in case of an Admin instance) to be updated INDEPENDENTLY of its other fields. A NON-empty User instance should ALWAYS retain a NON-empty last_login value FOR EACH of its associated Platform instances - see the edit_user_profile() and recover_user_profile() views. On the other hand, an empty User instance generally retains an empty last_login value for its ONLY associated Platform instance - see the import_user_credentials() view - but can also retain a NON-empty one for it (the user logs in and, although his/her profile is empty, he/she opts to log out - or is logged out automatically due to session expiration - WITHOUT filling it out). An Admin instance generally retains a NON-empty last_login value for the SLUB "platform" as it should be logged in to access any Admin-targeted functionality/view, but can also retain an empty one for it (the user has NEVER logged in but his/her profile has ALREADY been filled out in a NON-session context, e.g. fixture, interactive shell, file such as populateDB.py, etc. - see the relative references above).
# Given that support for time zones is enabled (USE_TZ setting is True), DateTimeField values (datetime objects) are stored in UTC format in usermergeDB and are generally displayed in the current time zone, that is 'Europe/Athens' by default (specified by the TIME_ZONE setting), in templates. It is possible to manually convert these values to a different time zone or/and change their display format if needed, e.g. convert them to local time (default time zone, i.e. 'Europe/Athens') or/and display them in 'DD/MM/YYYY HH:MM:SS' format. For more information on time zone support and datetime objects, see https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/ and the "Internationalization" part of settings.py . # Given that support for time zones is enabled (USE_TZ setting is True), DateTimeField values (datetime objects) are stored in UTC format in usermergeDB and are generally displayed in the current time zone, that is 'Europe/Athens' by default (specified by the TIME_ZONE setting), in templates. It is possible to manually convert these values to a different time zone or/and change their display format if needed, e.g. convert them to local time (default time zone, i.e. 'Europe/Athens') or/and display them in 'DD/MM/YYYY HH:MM:SS' format. For more information on time zone support and datetime objects, see https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/ and the "Internationalization" part of settings.py .
# The get_session_auth_hash() methods and is_authenticated attributes are needed for user authentication, e.g. in the login() and get_user() functions defined in auth.py and login_required() decorator used in views.py and decorators.py . Original source code for these methods/attributes can be found in https://github.com/django/django/blob/master/django/contrib/auth/base_user.py (for more information, see https://docs.djangoproject.com/en/2.0/topics/auth/customizing/). # The get_session_auth_hash() methods and is_authenticated attributes are needed for user authentication, e.g. in the login() and get_user() functions defined in auth.py and login_required() decorator used in views.py and decorators.py . Original source code for these methods/attributes can be found in https://github.com/django/django/blob/master/django/contrib/auth/base_user.py (for more information, see https://docs.djangoproject.com/en/2.0/topics/auth/customizing/).
......
...@@ -12,5 +12,10 @@ ...@@ -12,5 +12,10 @@
</nav> </nav>
<h4>Αρχική Σελίδα Διαχειριστή</h4> <h4>Αρχική Σελίδα Διαχειριστή</h4>
<nav id="home_nav">
<p><a href="{% url 'default_user_credentials_import' %}">Προσθήκη Διαπιστευτηρίων Χρηστών</a></p>
<p><a href="{% url 'default_user_report_creation' %}">Δημιουργία Αναφοράς Χρηστών</a></p>
<p><a href="{% url 'logout' %}">Έξοδος</a></p> <p><a href="{% url 'logout' %}">Έξοδος</a></p>
</nav>
{% endblock %} {% endblock %}
{% extends 'base.html' %}
{% block title %}
SLUB - Προσθήκη Διαπιστευτηρίων Χρηστών
{% endblock %}
{% block content %}
<nav id="session_nav">
Έχετε εισέλθει ως <strong>{{ user.first_name }} {{ user.last_name }}</strong>!
| <a href="{% url 'admin_home' %}">Αρχική Σελίδα</a>
| <a href="{% url 'logout' %}">Έξοδος</a>
</nav>
<h4>Προσθήκη Διαπιστευτηρίων Χρηστών</h4>
<form id="user_credentials_import_form" accept-charset="utf-8" enctype="multipart/form-data"
action="{% url 'import_user_credentials' %}" method="post">
{% csrf_token %}
<table>
<tbody>
<tr>
<td style="text-align:right;"><label for="platform">Πλατφόρμα διαπίστευσης:</label></td>
<td style="text-align:left;">
<select name="platform" id="platform" required="required">
<option value=""></option>
{% for name in platform_names %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td style="text-align:right;">
<label for="credentials_file">Αρχείο διαπιστευτηρίων<span style="color:darkorange;">*</span>:</label>
</td>
<td style="text-align:left;">
<input type="file" accept="text/plain" name="credentials_file" id="credentials_file" required="required" />
</td>
</tr>
</tbody>
</table>
<p style="color:darkorange;">
<strong>
* Το αρχείο διαπιστευτηρίων πρέπει να είναι ένα μη-κενό απλό αρχείο κειμένου, να έχει μέγεθος έως 40KB και να <br />
περιλαμβάνει μόνο γραμμές της μορφής &lt;Login: X Password: Y&gt; (χωρίς τις γωνιακές αγκύλες) όπου X είναι <br />
ένα μη-κενό όνομα χρήστη, Y είναι ένας μη-κενός κωδικός πρόσβασης και κανένα από τα δύο δεν περιέχει τη <br />
συμβολοσειρά &lt; Password: &gt; (χωρίς τις γωνιακές αγκύλες) ή/και χαρακτήρες μη-αποκωδικοποιήσιμους σε UTF-8!
</strong>
</p>
<input type="submit" value="Προσθήκη" />
</form>
{% if success_message %}
<p style="color:green;">
<img src="images/tick.png" alt="Επιβεβαίωση:" />
<strong>{{ success_message | linebreaksbr }}</strong>
</p>
{% endif %}
{% if error_messages %}
{% for message in error_messages %}
<p style="color:red;">
<img src="images/warning.svg" alt="Σφάλμα:" />
<strong>{{ message | linebreaksbr }}</strong>
</p>
{% endfor %}
{% endif %}
<p><a href="{% url 'admin_home' %}">&#x21B5; Επιστροφή στην Αρχική Σελίδα</a></p>
{% endblock %}
{% extends 'base.html' %}
{% block title %}
SLUB - Δημιουργία Αναφοράς Χρηστών
{% endblock %}
{% block content %}
<nav id="session_nav">
Έχετε εισέλθει ως <strong>{{ user.first_name }} {{ user.last_name }}</strong>!
| <a href="{% url 'admin_home' %}">Αρχική Σελίδα</a>
| <a href="{% url 'logout' %}">Έξοδος</a>
</nav>
<h4>Δημιουργία Αναφοράς Χρηστών</h4>
<form id="user_report_creation_form" accept-charset="utf-8" action="{% url 'create_user_report' %}" method="post">
{% csrf_token %}
<table>
<tbody>
<tr>
<td style="text-align:right;"><label for="platform">Πλατφόρμα αναφοράς:</label></td>
<td style="text-align:left;">
<select name="platform" id="platform" required="required">
<option value=""></option>
{% for name in platform_names %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td style="text-align:right;">
<label for="last_login">
Tελευταία είσοδος με διαπιστευτήρια <br />
της πλατφόρμας αναφοράς:
</label>
</td>
<td style="text-align:left;">
<select name="last_login" id="last_login" required="required">
<option value=""></option>
<option value="last_six_months">εντός του τελευταίου εξαμήνου</option>
<option value="last_year">εντός του τελευταίου έτους</option>
<option value="all_time">από την αρχή του χρόνου</option>
</select>
</td>
</tr>
</tbody>
</table>
<br />
<input type="submit" value="Δημιουργία" />
</form>
{% if error_messages %}
{% for message in error_messages %}
<p style="color:red;">
<img src="images/warning.svg" alt="Σφάλμα:" />
<strong>{{ message | linebreaksbr }}</strong>
</p>
{% endfor %}
{% endif %}
<p><a href="{% url 'admin_home' %}">&#x21B5; Επιστροφή στην Αρχική Σελίδα</a></p>
{% endblock %}
...@@ -24,10 +24,17 @@ urlpatterns = [ ...@@ -24,10 +24,17 @@ urlpatterns = [
re_path(r'^user/home$', views.display_user_home_page, name = 'user_home'), re_path(r'^user/home$', views.display_user_home_page, name = 'user_home'),
re_path(r'^user/profile/edit/default$', views.display_default_user_profile_edit_page, name = 'default_user_profile_edit'), re_path(r'^user/profile/edit/default$', views.display_default_user_profile_edit_page, name = 'default_user_profile_edit'),
re_path(r'^user/profile/edit/submit$', views.edit_user_profile, name = 'edit_user_profile'), re_path(r'^user/profile/edit/submit$', views.edit_user_profile, name = 'edit_user_profile'),
re_path(r'^user/profile/recovery/default$', views.display_default_user_profile_recovery_page, name = 'default_user_profile_recovery'), re_path(r'^user/profile/recovery/default$', views.display_default_user_profile_recovery_page,
name = 'default_user_profile_recovery'),
re_path(r'^user/profile/recovery/search$', views.search_for_recovery_user_profile, name = 'search_for_recovery_user_profile'), re_path(r'^user/profile/recovery/search$', views.search_for_recovery_user_profile, name = 'search_for_recovery_user_profile'),
re_path(r'^user/profile/recovery/submit$', views.recover_user_profile, name = 'recover_user_profile'), re_path(r'^user/profile/recovery/submit$', views.recover_user_profile, name = 'recover_user_profile'),
re_path(r'^admin/home$', views.display_admin_home_page, name = 'admin_home'), re_path(r'^admin/home$', views.display_admin_home_page, name = 'admin_home'),
re_path(r'^admin/user-credentials-import/default$', views.display_default_user_credentials_import_page,
name = 'default_user_credentials_import'),
re_path(r'^admin/user-credentials-import/submit$', views.import_user_credentials, name = 'import_user_credentials'),
re_path(r'^admin/user-report-creation/default$', views.display_default_user_report_creation_page,
name = 'default_user_report_creation'),
re_path(r'^admin/user-report-creation/submit$', views.create_user_report, name = 'create_user_report'),
re_path(r'^logout$', views.log_out, name = 'logout'), re_path(r'^logout$', views.log_out, name = 'logout'),
] ]
...@@ -18,6 +18,16 @@ def platform_is_SLUB_or_exists_in_DB(platform): ...@@ -18,6 +18,16 @@ def platform_is_SLUB_or_exists_in_DB(platform):
raise ValidationError('Το όνομα πλατφόρμας πρέπει να επιλέγεται μεταξύ εκείνων\n' raise ValidationError('Το όνομα πλατφόρμας πρέπει να επιλέγεται μεταξύ εκείνων\n'
'που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!', code = 'platform_is_not_SLUB_and_does_not_exist_in_DB') 'που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!', code = 'platform_is_not_SLUB_and_does_not_exist_in_DB')
def platform_exists_in_DB(platform):
"""
Ensure that the platform (name) is selected among the ones provided in the corresponding form drop-down list (this list includes the
platforms that exist in usermergeDB). If it is not (e.g. it is misedited via JavaScript), raise a ValidationError exception with the
appropriate error message and code.
"""
if not platform in get_all_platform_names_from_DB():
raise ValidationError('Το όνομα πλατφόρμας πρέπει να επιλέγεται μεταξύ εκείνων\n'
'που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!', code = 'platform_does_not_exist_in_DB')
def ece_id_is_not_031YY000(ece_id): def ece_id_is_not_031YY000(ece_id):
""" """
Ensure that the ece_id (format) is not 031YY000. Ensure that the ece_id (format) is not 031YY000.
...@@ -25,3 +35,14 @@ def ece_id_is_not_031YY000(ece_id): ...@@ -25,3 +35,14 @@ def ece_id_is_not_031YY000(ece_id):
""" """
if ece_id[5:] == '000': if ece_id[5:] == '000':
raise ValidationError('Ο αριθμός μητρώου απαγορεύεται να είναι της μορφής 031YY000 (YY: έτος)!', code = 'ece_id_is_031YY000') raise ValidationError('Ο αριθμός μητρώου απαγορεύεται να είναι της μορφής 031YY000 (YY: έτος)!', code = 'ece_id_is_031YY000')
def last_login_is_selected_from_provided_list(last_login):
"""
Ensure that the last login (search period) is selected among the ones provided in the corresponding form drop-down list.
If it is not (e.g. it is misedited via JavaScript), raise a ValidationError exception with the appropriate error message and code.
"""
if not last_login in ['last_six_months', 'last_year', 'all_time']:
raise ValidationError('Η περίοδος αναζήτησης τελευταίας εισόδου πρέπει να επιλέγεται μεταξύ\n'
'εκείνων που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!',
code = 'last_login_is_not_selected_from_provided_list')
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment