Commit 95a43695 authored by Giorgos Kazelidis's avatar Giorgos Kazelidis

- Defined __str__() methods for all the application models

- Removed the last_login field from the User model, inserted it to the Registry model (a User instance retains now one last_login value for each Platform instance that is associated with it via the corresponding Registry instance) and modified the emission arguments of the user_logged_in signal for logged-in User instances
- Constrained the email fields of the User/Admin models and user profile edit/recovery forms to a maximum length of 100 characters
- Constrained the username and password fields of the login form to a minimum length of 8 characters
- Omitted the redundant ECE-NTUA platform from the test Platform instances that are used to populate the usermerge_platform table of usermergeDB
- Omitted the username format check {piYYbSSS with SSS <> 000} of the login form for the Novice and Grader platforms as there can exist valid usernames for them that are not in the aforementioned format
- Enabled the Django admin site, registered all the application models to it and provided instructions on accessing it
- Defined the User_login_required() and Admin_login_required() decorators and used them for enhanced access control in the majority of views
- Reduced the database queries made in recover_user_profile() view to enhance its performance
- Added the production_logs folder to the .gitignore file
parent e324d683
......@@ -5,5 +5,8 @@
# Python compiled bytecode files
*.pyc
# virtual environment folder
# virtual environment folder - see README.md
venv/
# folder that contains the log files created in production - see the "Logging" part of settings.py
myprj/usermerge/production_logs/
......@@ -86,7 +86,7 @@
sudo apt-get install mysql-server
mysql_secure_installation
&nbsp; &nbsp; &nbsp; (7) use the password that was set for the root user during MySQL server installation as the DATABASES['default']['PASSWORD'] value in **~/Desktop/userbase/myprj/myprj/settings.py**
&nbsp; &nbsp; &nbsp; (8) copy the time zone definitions of the system's zoneinfo database, which is located in the /usr/share/zoneinfo/ directory of Ubuntu and most other Unix-like systems, to the "mysql" database without worrying about some possible "Unable to load/Skipping" warnings (you will be asked to provide the password that was set for the MySQL root user) and restart MySQL server (you can skip this substep if you have already executed it once):
&nbsp; &nbsp; &nbsp; (8) copy the time zone definitions of the system's zoneinfo database, which is located in the /usr/share/zoneinfo/ directory of Ubuntu and most other Unix-like systems, to the "mysql" database without worrying about some possible "Unable to load/Skipping" warnings (you will be asked to provide the password of the MySQL root user) and restart MySQL server (you can skip this substep if you have already executed it once):
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
sudo service mysql restart
......@@ -96,7 +96,7 @@
&nbsp; &nbsp; &nbsp; (10) open MySQL console with root privileges:
python manage.py dbshell
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ALTERNATIVELY, you could run the following command by providing the password that was set for the MySQL root user:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ALTERNATIVELY, you could run the following command by providing the password of the MySQL root user:
mysql -u root -p
&nbsp; &nbsp; &nbsp; (11) if the project database (usermergeDB) has already been created (`SHOW DATABASES;` to check), delete it:
......@@ -114,18 +114,14 @@
python manage.py migrate
&nbsp; &nbsp; &nbsp; (17) if you have previously set SET_DEFAULT_STORAGE_ENGINE_TO_INNODB to "True", set it back to "False"
&nbsp; &nbsp; &nbsp; (18) open Python interpreter in interactive mode:
&nbsp; &nbsp; &nbsp; (18) populate the project database with test data by executing the populateDB.py script of the application directory (usermerge) via the Python interpreter:
python manage.py shell
&nbsp; &nbsp; &nbsp; (19) populate the project database with test data, i.e. execute the populateDB.py script of the application directory (usermerge), by typing the following line and pressing `Enter` twice:
with open('./usermerge/populateDB.py') as popDB: exec(popDB.read())
&nbsp; &nbsp; &nbsp; (20) close Python interpreter by pressing `Ctrl-D`
&nbsp; &nbsp; &nbsp; (21) deactivate the virtual environment:
python manage.py shell --command="with open('./usermerge/populateDB.py') as popDB: exec(popDB.read())"
&nbsp; &nbsp; &nbsp; (19) deactivate the virtual environment:
deactivate
## RUNNING THE PROJECT (INSIDE THE VIRTUAL ENVIRONMENT)
## ACCESSING THE DJANGO ADMIN SITE OR RUNNING THE PROJECT (AFTER STARTING THE DJANGO DEVELOPMENT SERVER INSIDE THE VIRTUAL ENVIRONMENT)
(A) activate the virtual environment:
cd ~/Desktop/userbase
......@@ -133,12 +129,18 @@
(B) navigate to the project directory:
cd myprj
(C\) start the Django development server (at http://127.0.0.1:8000/):
(C\) create a superuser account in the project database (usermergeDB) if you have not already done so (this step is necessary for accessing the Django admin site) [sources:
&nbsp; &nbsp; &nbsp; -- https://docs.djangoproject.com/en/2.0/ref/contrib/admin/
&nbsp; &nbsp; &nbsp; ]:
&nbsp; &nbsp; &nbsp; * ATTENTION: while creating the superuser account, you will be asked to set the corresponding credentials (username and password) and (optional) email.
python manage.py createsuperuser
(D) start the Django development server (at http://127.0.0.1:8000/):
python manage.py runserver
(D) navigate to http://127.0.0.1:8000/ via web browser to run the project
(E) access the Django admin site by navigating to http://127.0.0.1:8000/admin/ (via web browser) and filling out the emerging login form with the credentials of the aforementioned superuser account OR run the project by navigating to http://127.0.0.1:8000/
## STOPPING THE PROJECT (AND EXITING THE VIRTUAL ENVIRONMENT)
## STOPPING THE DJANGO DEVELOPMENT SERVER (AND EXITING THE VIRTUAL ENVIRONMENT)
(A) stop the Django development server (running at http://127.0.0.1:8000/):
&nbsp; &nbsp; &nbsp; -- return to the terminal where you have run `python manage.py runserver` and press `Ctrl-C`
(B) deactivate the virtual environment:
......
......@@ -37,7 +37,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'usermerge',
'usermerge.apps.UsermergeConfig',
]
MIDDLEWARE = [
......@@ -74,9 +74,7 @@ WSGI_APPLICATION = 'myprj.wsgi.application'
# Authentication
# https://docs.djangoproject.com/en/2.0/topics/auth/
AUTHENTICATION_BACKENDS = ['usermerge.backends.UserBackend']
LOGIN_URL = 'default_login'
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'usermerge.backends.AppUserModelBackend']
# Logging
......
from django.contrib import admin
from .models import Admin, Platform, Registry, User
# Register your models here.
# https://docs.djangoproject.com/en/2.0/ref/contrib/admin/
admin.site.register(User)
admin.site.register(Platform)
admin.site.register(Registry)
admin.site.register(Admin)
from django.apps import AppConfig
# Create your project-specific configuration classes (AppConfig subclasses) for the usermerge application here.
# https://docs.djangoproject.com/en/2.0/ref/applications/
class UsermergeConfig(AppConfig):
name = 'usermerge'
......@@ -8,11 +8,11 @@ from django.contrib.auth import _get_backends, load_backend
from django.contrib.auth.signals import user_logged_in
from django.middleware.csrf import rotate_token
from django.utils.crypto import constant_time_compare
from usermerge.models import Registry
from .models import Registry
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/ and backends.py .
# The user_logged_in signal is defined (alongside the other authentication signals, user_login_failed and user_logged_out) in the django.contrib.auth.signals module (https://github.com/django/django/blob/master/django/contrib/auth/signals.py). When a user logs in successfully, an appropriate user_logged_in signal is sent by the corresponding user model (User/Admin) in the context of the below defined login() function and is instantaneously received by the update_last_login() function (https://github.com/django/django/blob/master/django/contrib/auth/apps.py). update_last_login() then updates the last_login field of the corresponding user instance to the date-time of the signal reception (https://github.com/django/django/blob/master/django/contrib/auth/models.py). For more information on the user models and their last_login fields, see models.py . For more information on signals, see https://docs.djangoproject.com/en/2.0/topics/signals/ .
# For more information on the platform names that are provided in the drop-down list of the login form, see log_in() view.
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/, backends.py and AuthenticationMiddleware of middleware.py .
# For more information on the utilization of the login form input to perform user authentication and login, see the log_in() view.
# The user_logged_in signal is defined (alongside the other authentication signals, user_login_failed and user_logged_out) in the django.contrib.auth.signals module (https://github.com/django/django/blob/master/django/contrib/auth/signals.py). When a user (User/Admin instance) logs in successfully, an appropriate user_logged_in signal is sent by the corresponding user model (Registry model in case of a User instance) in the context of the below defined login() function and is instantaneously received by the update_last_login() function (https://github.com/django/django/blob/master/django/contrib/auth/apps.py). update_last_login() then updates the last_login field of the corresponding user instance for the login platform (SLUB in case of an Admin instance) to the date-time of the signal reception (https://github.com/django/django/blob/master/django/contrib/auth/models.py). For more information on the user models and their last_login fields, see models.py . For more information on signals, see https://docs.djangoproject.com/en/2.0/topics/signals/ .
SESSION_KEY = '_auth_user_id'
PLATFORM_SESSION_KEY = '_auth_platform'
......@@ -28,9 +28,9 @@ def _get_user_model_label(request):
# and user_model_name is the name of some user model (User/Admin) defined in the application models (see models.py).
# WARNING: The model officially used for user representation is stored as label in the AUTH_USER_MODEL setting and is generally based
# either on the AbstractBaseUser or on the AbstractUser model (see https://docs.djangoproject.com/en/2.0/topics/auth/customizing/).
# This means that AUTH_USER_MODEL should, among others, have a password field and possibly a username field, which is NOT true for
# the primary user model of the usermerge application, 'usermerge.User'. To bypass this issue during authentication, AUTH_USER_MODEL
# is omitted and 'usermerge.User' is used directly alongside usermerge's secondary user model, 'usermerge.Admin'. Although the latter
# This means that AUTH_USER_MODEL should, among others, have a password and a (unique) username field, which is NOT true for the
# primary user model of the usermerge application, 'usermerge.User'. To bypass this issue during authentication, AUTH_USER_MODEL is
# omitted and 'usermerge.User' is used directly alongside usermerge's secondary user model, 'usermerge.Admin'. Although the latter
# has both a username and a password field, it does NOT inherit from the AbstractBaseUser or AbstractUser model for simplicity as
# well as peer-conformance reasons. AUTH_USER_MODEL is undeclared in settings.py, i.e. 'auth.User' (AbstractUser-inherited model) by
# default, and should remain that way as it is practically replaced by 'usermerge.User' and 'usermerge.Admin'.
......@@ -41,7 +41,7 @@ def _get_user_model_label(request):
def get_user_model(request):
"""
Return the user model that is active in the session after specifying its label - see _get_user_model_label() function above.
Return the user model that is active in the session after specifying its label - see the _get_user_model_label() function above.
"""
return django_apps.get_model(_get_user_model_label(request), require_ready = False)
......@@ -55,6 +55,7 @@ def login(request, user, backend = None):
Note that data set during the anonymous session is retained when the user logs in.
"""
platform_name = request.POST['platform']
registry = None
session_auth_hash = ''
if user is None:
user = request.user
......@@ -99,6 +100,20 @@ def login(request, user, backend = None):
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
# If the just logged-in user is an Admin instance, its last_login value for the login platform (SLUB) is kept by the user instance
# itself; therefore, send the user_logged_in signal with the Admin model as sender argument, the associated request as request
# argument and the aforementioned Admin instance as user argument. On the other hand, if the just logged-in user is a User instance,
# its last_login value for the login platform is kept by the corresponding Registry instance; therefore, send the user_logged_in
# signal with the Registry model as sender argument, the associated request as request argument and the aforementioned Registry
# instance as user argument (if the logged-in user or/and login platform instances are ever needed in any receiver function of the
# signal, they can be easily specified either via the session data of the request or via the Registry instance). For more information
# on sending the user_logged_in signal, see the relative comment at the top.
if registry:
# User login signal emission
user_logged_in.send(sender = registry.__class__, request = request, user = registry)
else:
# Admin login signal emission
user_logged_in.send(sender = user.__class__, request = request, user = user)
def get_user(request):
......
from django.contrib.auth.hashers import check_password, make_password
from usermerge.models import Admin, Registry, User
from .models import Admin, Registry, User
# Create your backends here.
# https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#authentication-backends
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/ and auth.py .
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/, auth.py and AuthenticationMiddleware of middleware.py .
class AppUserModelBackend:
"""
Authenticates against the application user models, i.e. usermerge.User and usermerge.Admin models.
"""
class UserBackend:
def authenticate(self, request, platform = None, username = None, password = None):
"""
Authenticate the provided credentials, i.e. username and password, for the selected platform.
......
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from functools import wraps
# Create your decorators here.
# There are many different types of decorators in Django, such as view decorators (https://docs.djangoproject.com/en/2.0/topics/http/decorators/), authentication decorators (https://docs.djangoproject.com/en/2.0/topics/auth/default/), utility decorators (https://docs.djangoproject.com/en/2.0/ref/utils/), etc. For more information, search in https://docs.djangoproject.com/en/2.0/search/?q=decorator .
# For more information on decorator definition and usage, see https://realpython.com/primer-on-python-decorators/ .
# For more information on view-related subjects, e.g. request/response objects, exceptions that trigger built-in error views, default and application user models (auth.User and usermerge.{User/Admin}), named URL patterns, etc., see views.py .
def User_login_required(view):
"""
Ensure that the current user has logged in and that his/her model is usermerge.User in order to execute the wrapped view. If he/she
has not logged in (his/her model is AnonymousUser), redirect him/her to the login page. If he/she has logged in but his/her model is
either auth.User or usermerge.Admin, show him/her an HTTP 403 (Forbidden) page by raising a PermissionDenied exception.
"""
def User_model_required(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if str(request.user.__class__) == "<class 'usermerge.models.User'>":
return func(request, *args, **kwargs)
else: # either auth.User or usermerge.Admin model
raise PermissionDenied
return wrapper
# The login and user model checks are performed by the login_required() and User_model_required() decorators respectively.
return login_required(User_model_required(view), None, 'default_login')
def Admin_login_required(view):
"""
Ensure that the current user has logged in and that his/her model is usermerge.Admin in order to execute the wrapped view. If he/she
has not logged in (his/her model is AnonymousUser), redirect him/her to the login page. If he/she has logged in but his/her model is
either auth.User or usermerge.User, show him/her an HTTP 403 (Forbidden) page by raising a PermissionDenied exception.
"""
def Admin_model_required(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if str(request.user.__class__) == "<class 'usermerge.models.Admin'>":
return func(request, *args, **kwargs)
else: # either auth.User or usermerge.User model
raise PermissionDenied
return wrapper
# The login and user model checks are performed by the login_required() and Admin_model_required() decorators respectively.
return login_required(Admin_model_required(view), None, 'default_login')
import re
from django import forms
from usermerge.validators import platform_is_selected_from_provided_list, ece_id_is_not_031YY000
from .validators import ece_id_is_not_031YY000, platform_is_SLUB_or_exists_in_DB
# Create your forms here.
# https://docs.djangoproject.com/en/2.0/ref/forms/
# Each programmatic form from the ones defined below is based format-wise on the similarly named template form (e.g. LoginForm is based on the login_form of login.html, UserProfileEditForm is based on the user_profile_edit_form of user_profile_edit.html, etc.) and constraint-wise on the application models (see models.py).
# Each (programmatic) form from the ones defined below is based format-wise on the similarly named template form (e.g. LoginForm is based on the login_form of login.html, UserProfileEditForm is based on the user_profile_edit_form of user_profile_edit.html, etc.) and constraint-wise on the application models (see models.py).
class LoginForm(forms.Form):
platform = forms.CharField(error_messages = {'required': 'Το όνομα πλατφόρμας πρέπει απαραίτητα να επιλεχθεί και να είναι μη-κενό!'},
validators = [platform_is_selected_from_provided_list])
username = forms.CharField(max_length = 50,
validators = [platform_is_SLUB_or_exists_in_DB])
username = forms.CharField(min_length = 8, max_length = 50,
error_messages = {'required': 'Το όνομα χρήστη πρέπει απαραίτητα να συμπληρωθεί!',
'min_length': 'Το όνομα χρήστη πρέπει να έχει τουλάχιστον 8 χαρακτήρες!',
'max_length': 'Το όνομα χρήστη δεν πρέπει να υπερβαίνει τους 50 χαρακτήρες!'})
password = forms.CharField(max_length = 50,
password = forms.CharField(min_length = 8, max_length = 50,
error_messages = {'required': 'Ο κωδικός πρόσβασης πρέπει απαραίτητα να συμπληρωθεί!',
'min_length': 'Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες!',
'max_length': 'Ο κωδικός πρόσβασης δεν πρέπει να υπερβαίνει τους 50 χαρακτήρες!'})
def clean(self):
cleaned_data = super().clean()
# If platform and username have survived the initial individual field checks (by the time this method is called, all the
# individual field clean methods will have been run, so cleaned_data will be populated with any data that has survived so far)
# and platform refers either to Novice or to Grader, ensure that the username format is piYYbSSS (if it is not, raise a
# ValidationError exception with the appropriate error message and code), where YY refers to year and SSS refers to non-000
# serial number.
if set(('platform', 'username')).issubset(cleaned_data):
platform = cleaned_data['platform']
username = cleaned_data['username']
if platform == 'Novice' or platform == 'Grader':
if re.fullmatch('pi[0-9]{2}b[0-9]{3}', username):
if username[5:] == '000':
raise forms.ValidationError('Το όνομα χρήστη στις πλατφόρμες Novice και Grader απαγορεύεται να\n'
'είναι της μορφής piYYb000 (YY: έτος)!', code = 'novice_or_grader_username_is_piYYb000')
else:
raise forms.ValidationError('Το όνομα χρήστη στις πλατφόρμες Novice και Grader πρέπει να\n'
'είναι της μορφής piYYbSSS (ΥΥ: έτος, SSS: αύξων αριθμός)!',
code = 'novice_or_grader_username_format_is_not_piYYbSSS')
class UserProfileEditForm(forms.Form):
first_name = forms.CharField(max_length = 50,
error_messages = {'required': 'Το όνομα πρέπει απαραίτητα να συμπληρωθεί!',
......@@ -51,9 +33,9 @@ class UserProfileEditForm(forms.Form):
'invalid': 'Ο αριθμός μητρώου πρέπει να είναι της μορφής '
'031YYSSS (YY: έτος, SSS: αύξων αριθμός)!'},
validators = [ece_id_is_not_031YY000])
email = forms.EmailField(max_length = 254,
email = forms.EmailField(max_length = 100,
error_messages = {'required': 'Το ηλεκτρονικό ταχυδρομείο πρέπει απαραίτητα να συμπληρωθεί!',
'max_length': 'Το ηλεκτρονικό ταχυδρομείο δεν πρέπει να υπερβαίνει τους 254 χαρακτήρες!',
'max_length': 'Το ηλεκτρονικό ταχυδρομείο δεν πρέπει να υπερβαίνει τους 100 χαρακτήρες!',
'invalid': 'Το ηλεκτρονικό ταχυδρομείο πρέπει να είναι της μορφής local-part@domain που\n'
'περιγράφεται στην ιστοσελίδα https://en.wikipedia.org/wiki/Email_address !'})
......
......@@ -4,22 +4,19 @@ Set of helper functions that contribute to the correct execution and enhanced st
from django import forms
from django.template.response import TemplateResponse
from usermerge.auth import PLATFORM_SESSION_KEY
from usermerge.models import Platform
from .auth import PLATFORM_SESSION_KEY
from .models import Platform
# Create your helper functions here.
# For more information on view-related subjects, e.g. using request/response objects, making database queries, working with forms, etc., see views.py .
def get_names_of_SoftLab_provided_platforms_from_DB():
def get_all_platform_names_from_DB():
"""
Return the id-ordered list of all the platform names, except for ECE-NTUA (this "platform" is not provided by SoftLab), that exist
in usermergeDB. This list can, among others, be used in the context of application pages/templates to display the name options of
the platform field in forms.
Return the list (QuerySet) of all the platform names that exist in usermergeDB. This list can, among others, be used in the context
of application pages/templates to display the name options of the platform field in forms.
"""
platform_names = list(Platform.objects.order_by('id').values_list('name', flat = True))
platform_names.remove('ECE-NTUA') # For more information on platforms that exist in usermergeDB, see populateDB.py .
return platform_names
return Platform.objects.values_list('name', flat = True)
def get_form_error_messages(form):
"""
......
from django.conf import settings
from django.contrib import auth as django_contrib_auth
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
from usermerge import auth
from . import auth as usermerge_auth
# Create your middleware here.
# https://docs.djangoproject.com/en/2.0/topics/http/middleware/
# AuthenticationMiddleware is a middleware component that associates the current user with every incoming web request. Original source code for the get_user() function and the aforementioned component can be found in https://github.com/django/django/blob/master/django/contrib/auth/middleware.py (for more information, see https://docs.djangoproject.com/en/2.0/ref/middleware/#django.contrib.auth.middleware.AuthenticationMiddleware).
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/, auth.py and backends.py .
# The login() function of the application's authentication module (usermerge.auth) is used to log a user (usermerge.{User/Admin} instance) in via the login page of the application - see the log_in() view - and inserts the PLATFORM_SESSION_KEY value into the (dictionary-like) session data based on the platform selected in the corresponding form. On the other hand, the login() function of the default authentication module (django.contrib.auth) is used to log a user (auth.User instance) in via the login page of the admin site (see https://docs.djangoproject.com/en/2.0/ref/contrib/admin/) and does not insert any PLATFORM_SESSION_KEY value into the session data as the corresponding form does not include any platform field.
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
# If the PLATFORM_SESSION_KEY value exists in the session data, retrieve the user instance associated with the given request
# session by using the get_user() function of the application's authentication module. Otherwise, retrieve it by using the
# get_user() function of the default authentication module. For more information, see the relative comment at the top.
if usermerge_auth.PLATFORM_SESSION_KEY in request.session:
request._cached_user = usermerge_auth.get_user(request)
else:
request._cached_user = django_contrib_auth.get_user(request)
return request._cached_user
class AuthenticationMiddleware(MiddlewareMixin):
......@@ -20,3 +29,4 @@ class AuthenticationMiddleware(MiddlewareMixin):
'insert "django.contrib.sessions.middleware.SessionMiddleware" before "usermerge.middleware.AuthenticationMiddleware".'
) % ('_CLASSES' if settings.MIDDLEWARE is None else '')
request.user = SimpleLazyObject(lambda: get_user(request))
# Generated by Django 2.0.4 on 2019-01-24 11:33
# Generated by Django 2.0.4 on 2019-05-07 19:10
from django.db import migrations, models
import django.db.models.deletion
......@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=50)),
('email', models.EmailField(max_length=254, unique=True)),
('email', models.EmailField(max_length=100, unique=True)),
('username', models.CharField(max_length=50, unique=True)),
('password', models.CharField(max_length=100)),
('last_login', models.DateTimeField(null=True)),
......@@ -37,6 +37,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=50)),
('password', models.CharField(max_length=100)),
('last_login', models.DateTimeField(null=True)),
('platform', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='usermerge.Platform')),
],
options={
......@@ -50,8 +51,7 @@ class Migration(migrations.Migration):
('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=50)),
('ece_id', models.CharField(max_length=8, null=True, unique=True)),
('email', models.EmailField(max_length=254, null=True, unique=True)),
('last_login', models.DateTimeField(null=True)),
('email', models.EmailField(max_length=100, null=True, unique=True)),
],
),
migrations.AddField(
......
This diff is collapsed.
......@@ -3,31 +3,37 @@ Populate usermergeDB (see models.py) with test data to check the correctness of
"""
from django.contrib.auth.hashers import make_password
# Use "usermerge.models" instead of ".models" as the import source module to avoid raising a
# "No module named 'django.core.management.commands.models'" ImportError exception when this
# script is executed via the interactive shell (see README.md).
from usermerge.models import Admin, Platform, Registry, User
# Populate Admin table with test admins.
# Populate the usermerge_admin table with test Admin instances.
admin_hashed_password = make_password('7wonders')
admin1 = Admin.objects.create(first_name = 'Νικόλαος', last_name = 'Παπασπύρου', email = 'nikolaos@softlab.ntua.gr',
username = 'nikolaos', password = admin_hashed_password)
admin2 = Admin.objects.create(first_name = 'Ευστάθιος', last_name = 'Ζάχος', email = 'eustathios@corelab.ntua.gr',
username = 'eustathios', password = admin_hashed_password)
# Populate Platform table with test platforms.
ece_ntua = Platform.objects.create(name = 'ECE-NTUA')
# Populate the usermerge_platform table with test Platform instances.
novice = Platform.objects.create(name = 'Novice')
grader = Platform.objects.create(name = 'Grader')
moodle = Platform.objects.create(name = 'Moodle')
plgrader = Platform.objects.create(name = 'PLgrader')
# Populate User table with test users.
# The user1 and user2 instances are created with filled out fields. This creation method can SOMETIMES be used during development to assist testing/debugging, but it is NOT generally accepted and should ALWAYS be avoided in production. On the other hand, creating User instances with empty fields, such as user3, is the accepted method and should ALWAYS be preferred both during development and in production.
# Populate the usermerge_user table with test User instances.
# The user1 and user2 instances are created with filled out fields. This creation method can SOMETIMES be used during development to
# assist testing/debugging, but it is NOT generally accepted and should ALWAYS be avoided in production. On the other hand, creating
# User instances with empty fields, such as user3, is the accepted method and should ALWAYS be preferred both during development and
# in production.
user1 = User.objects.create(first_name = 'Γεώργιος', last_name = 'Καζελίδης', ece_id = '03199999',
email = 'gkazelid@undergraduate.ece.ntua.gr')
user2 = User.objects.create(first_name = 'Ζαχαρίας', last_name = 'Δόγκανος', email = 'zdogkanos@undergraduate.ece.ntua.gr')
user3 = User.objects.create()
# Populate Registry table with test registries.
# Populate the usermerge_registry table with test Registry instances.
user_hashed_password = make_password('password')
registry1 = Registry.objects.create(user = user1, platform = novice, username = 'pi99b999', password = user_hashed_password)
registry2 = Registry.objects.create(user = user2, platform = moodle, username = 'moodler', password = user_hashed_password)
registry3 = Registry.objects.create(user = user3, platform = plgrader, username = 'plgrad', password = user_hashed_password)
registry2 = Registry.objects.create(user = user2, platform = moodle, username = 'moodlemoot', password = user_hashed_password)
registry3 = Registry.objects.create(user = user3, platform = plgrader, username = 'proglang', password = user_hashed_password)
......@@ -9,5 +9,5 @@
{% block content %}
<h4>Σφάλμα 404 (Not Found)</h4>
<p>Η ζητηθείσα ιστοσελίδα "{{ request_path }}" δεν βρέθηκε στον παρόντα διακομιστή!</p>
<p>Η ζητηθείσα ιστοσελίδα δεν βρέθηκε στον παρόντα διακομιστή!</p>
{% endblock %}
......@@ -15,12 +15,11 @@
<tr>
<td style="text-align:right;"><label for="platform">Είσοδος ως:</label></td>
<td style="text-align:left;">
<select name="platform" id="platform">
<select name="platform" id="platform" required="required">
<option value=""></option>
<option value="SLUB">διαχειριστής του SLUB</option>
{% for name in platform_names %}
<option value="{{ name }}" {% if name == 'Novice' %}selected="selected"{% endif %}>
χρήστης του {{ name }}
</option>
<option value="{{ name }}">χρήστης του {{ name }}</option>
{% endfor %}
</select>
</td>
......@@ -28,13 +27,13 @@
<tr>
<td style="text-align:right;"><label for="username">Όνομα χρήστη:</label></td>
<td style="text-align:left;">
<input type="text" name="username" id="username" maxlength="50" required="required" />
<input type="text" name="username" id="username" pattern=".{8,}" maxlength="50" required="required" />
</td>
</tr>
<tr>
<td style="text-align:right;"><label for="password">Κωδικός πρόσβασης:</label></td>
<td style="text-align:left;">
<input type="password" name="password" id="password" maxlength="50" required="required" />
<input type="password" name="password" id="password" pattern=".{8,}" maxlength="50" required="required" />
</td>
</tr>
</tbody>
......
......@@ -14,3 +14,5 @@
- https://www.jsdelivr.com/
* <label> element usage:
- https://stackoverflow.com/questions/7636502/why-use-label
* Minimum length constraint for <input> elements:
- https://stackoverflow.com/questions/10281962/is-there-a-minlength-validation-attribute-in-html5
......@@ -33,7 +33,7 @@
<nav id="home_nav">
{% comment %}
The "Edit Profile" and "Log out" options are ALWAYS available to the user. ALL the OTHER
navigation options are available to him/her ONLY if he/she has filled out his/her profile.
navigation options are available to him/her ONLY if his/her profile is NON-empty.
{% endcomment %}
<p><a href="{% url 'default_user_profile_edit' user.id %}">Επεξεργασία Προφίλ</a></p>
{% if user.first_name != '' and user.last_name != '' and user.email is not None %}
......
......@@ -59,7 +59,7 @@
<tr>
<td style="text-align:right;"><label for="email">Ηλεκτρονικό ταχυδρομείο:</label></td>
<td style="text-align:left;">
<input type="email" name="email" id="email" maxlength="254"
<input type="email" name="email" id="email" maxlength="100"
{% if user.email is not None %}value="{{ user.email }}"{% endif %} required="required" />
</td>
</tr>
......
......@@ -35,7 +35,7 @@
<tr>
<td style="text-align:right;"><label for="email">Ηλεκτρονικό ταχυδρομείο:</label></td>
<td style="text-align:left;">
<input type="email" name="email" id="email" maxlength="254" required="required" />
<input type="email" name="email" id="email" maxlength="100" required="required" />
</td>
</tr>
</tbody>
......
......@@ -15,7 +15,7 @@ Including another URLconf
"""
from django.urls import re_path
from usermerge import views
from . import views
urlpatterns = [
# The default login page serves as index page. Therefore, its regular route becomes r'^$' instead of r'^login/default$'.
......
from django.core.exceptions import ValidationError
from usermerge.models import Platform
from .helper import get_all_platform_names_from_DB
from .models import Platform
# Create your validators here.
# https://docs.djangoproject.com/en/2.0/ref/validators/
# The below defined validators are mainly utilized by the application (programmatic) forms (see forms.py).
# The validators of a form field are run in the context of run_validators() method when the clean() method of the field is called. If the field value is empty, i.e. None, '', [], () or {}, run_validators() will NOT run the validators (see https://docs.djangoproject.com/en/2.0/_modules/django/forms/fields/). Therefore, it can be safely assumed that the single arguments of the following validators are NEVER empty.
# For more information on the platform names that are available to select from in forms, see get_names_of_SoftLab_provided_platforms_from_DB() function of helper.py . In the case of login form, see also login_form of login.html .
def platform_is_selected_from_provided_list(platform):
def platform_is_SLUB_or_exists_in_DB(platform):
"""
Ensure that the platform (name) 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.
Ensure that the platform (name) is selected among the ones provided in the corresponding form drop-down list (this list includes SLUB
as well as 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 platform != 'SLUB' and not (platform != 'ECE-NTUA' and platform in Platform.objects.values_list('name', flat = True)):
if platform != 'SLUB' and not platform in get_all_platform_names_from_DB():
raise ValidationError('Το όνομα πλατφόρμας πρέπει να επιλέγεται μεταξύ εκείνων\n'
'που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!', code = 'platform_is_not_selected_from_provided_list')
'που παρέχονται στην αντίστοιχη αναπτυσσόμενη λίστα!', code = 'platform_is_not_SLUB_and_does_not_exist_in_DB')
def ece_id_is_not_031YY000(ece_id):
"""
......
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