Commit 0320cadc authored by Giorgos Kazelidis's avatar Giorgos Kazelidis

- Wrapped the edit_user_profile(), recover_user_profile() and...

- Wrapped the edit_user_profile(), recover_user_profile() and import_user_credentials() views in the atomic() decorator to run database modification queries within an atomic transaction
- Utilized SELECT FOR UPDATE queries in the edit_user_profile() and recover_user_profile() views to lock database rows (model instances) intended for update during an atomic transaction
- Added the "PROJECT OVERVIEW" and "LIST OF SUGGESTED TO-DO ACTIONS" parts to README.md
- Added documentation links to the "ENVIRONMENT SPECIFICATIONS" part of README.md
- Changed the names of the Django root and project directories from myprj to slub
- Changed the name of helper.py module to helpers.py
parent 3fbfccbd
...@@ -9,4 +9,4 @@ ...@@ -9,4 +9,4 @@
venv/ venv/
# folder that contains the log files created in production - see the "Logging" part of settings.py # folder that contains the log files created in production - see the "Logging" part of settings.py
myprj/usermerge/production_logs/ slub/usermerge/production_logs/
## PROJECT OVERVIEW
* Type: Diploma thesis
* Subject: Development of the SoftLab UserBase (SLUB) web tool
* Author: Georgios Kazelidis \<giorgkazelidis@gmail.com>
* Supervisor: Nikolaos Papaspyrou \<nickie@softlab.ntua.gr>
* Copyright: -
* License: -
## ENVIRONMENT SPECIFICATIONS ## ENVIRONMENT SPECIFICATIONS
* Working OS: Ubuntu 16.04 LTS * Working OS: Ubuntu 16.04 LTS [https://www.ubuntu.com/]
* Python version: 3.5.2 (pre-installed) * Python version: 3.5.2 (pre-installed) [https://www.python.org/]
* Git version: 2.7.4 * Git version: 2.7.4 [https://git-scm.com/]
* pip3 version: 10.0.1 * pip3 version: 10.0.1 [https://pypi.org/project/pip/]
* virtualenv version: 15.2.0 * virtualenv version: 15.2.0 [https://pypi.org/project/virtualenv/]
* Django version: 2.0.4 * Django version: 2.0.4 [https://www.djangoproject.com/]
* pytz version: 2018.4 * pytz version: 2018.4 [https://pypi.org/project/pytz/]
* concurrent-log-handler version: 0.9.12 * concurrent-log-handler version: 0.9.12 [https://pypi.org/project/concurrent-log-handler/]
* mysqlclient version: 1.3.12 * mysqlclient version: 1.3.12 [https://pypi.org/project/mysqlclient/]
* MySQL server version: 5.7.24 * MySQL server version: 5.7.24 [https://dev.mysql.com/]
## DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT ## DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT
(A) install pip3 (`pip3 --version` to see if it is already installed): (A) install pip3 (`pip3 --version` to see if it is already installed):
...@@ -85,14 +93,14 @@ ...@@ -85,14 +93,14 @@
sudo apt-get install mysql-server sudo apt-get install mysql-server
mysql_secure_installation 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; (7) use the password that was set for the root user during MySQL server installation as the DATABASES['default']['PASSWORD'] value in **~/Desktop/userbase/slub/slub/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 of 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 mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
sudo service mysql restart sudo service mysql restart
&nbsp; &nbsp; &nbsp; (9) navigate to the project directory (myprj): &nbsp; &nbsp; &nbsp; (9) navigate to the project directory (slub):
cd myprj cd slub
&nbsp; &nbsp; &nbsp; (10) open MySQL console with root privileges: &nbsp; &nbsp; &nbsp; (10) open MySQL console with root privileges:
python manage.py dbshell python manage.py dbshell
...@@ -108,8 +116,8 @@ ...@@ -108,8 +116,8 @@
&nbsp; &nbsp; &nbsp; (13) exit MySQL console: &nbsp; &nbsp; &nbsp; (13) exit MySQL console:
EXIT; EXIT;
&nbsp; &nbsp; &nbsp; (14) use the collation that was selected during the project database creation as the DATABASES['default']['OPTIONS']['init_command']#collation_connection value in **~/Desktop/userbase/myprj/myprj/settings.py** &nbsp; &nbsp; &nbsp; (14) use the collation that was selected during the project database creation as the DATABASES['default']['OPTIONS']['init_command']#collation_connection value in **~/Desktop/userbase/slub/slub/settings.py**
&nbsp; &nbsp; &nbsp; (15) if MySQL server version is 5.5.4 and earlier, set SET_DEFAULT_STORAGE_ENGINE_TO_INNODB to "True" in **~/Desktop/userbase/myprj/myprj/settings.py** &nbsp; &nbsp; &nbsp; (15) if MySQL server version is 5.5.4 and earlier, set SET_DEFAULT_STORAGE_ENGINE_TO_INNODB to "True" in **~/Desktop/userbase/slub/slub/settings.py**
&nbsp; &nbsp; &nbsp; (16) initiate the project database (the tables are created during this substep): &nbsp; &nbsp; &nbsp; (16) initiate the project database (the tables are created during this substep):
python manage.py migrate python manage.py migrate
...@@ -128,7 +136,7 @@ ...@@ -128,7 +136,7 @@
source venv/bin/activate source venv/bin/activate
(B) navigate to the project directory: (B) navigate to the project directory:
cd myprj cd slub
(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: (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; -- https://docs.djangoproject.com/en/2.0/ref/contrib/admin/
&nbsp; &nbsp; &nbsp; ]: &nbsp; &nbsp; &nbsp; ]:
...@@ -138,7 +146,7 @@ ...@@ -138,7 +146,7 @@
(D) start the Django development server (at http://127.0.0.1:8000/): (D) start the Django development server (at http://127.0.0.1:8000/):
python manage.py runserver python manage.py runserver
(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/ (E) access the Django admin site by navigating to http://127.0.0.1:8000/django-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 DJANGO DEVELOPMENT SERVER (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/): (A) stop the Django development server (running at http://127.0.0.1:8000/):
...@@ -153,3 +161,12 @@ Provided you have already downloaded the (source code of the) project (that is, ...@@ -153,3 +161,12 @@ Provided you have already downloaded the (source code of the) project (that is,
rm -rf ~/Desktop/userbase rm -rf ~/Desktop/userbase
&nbsp; &nbsp; &nbsp; (2) follow steps (D) and (E) of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above &nbsp; &nbsp; &nbsp; (2) follow steps (D) and (E) of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above
## LIST OF SUGGESTED TO-DO ACTIONS
* Create and use a Makefile for setting up/updating the project [https://www.gnu.org/software/make/manual/make.html]
* Create and use a criteria-based (e.g. last_login-based) batch deletion view for User/Registry instances to ensure that the project database (usermergeDB) remains "small" and thus efficient to interact with - see models.py
* Create and run unit tests [https://docs.djangoproject.com/en/2.0/topics/testing/]
* Make better use of the Django admin site capabilities
* Make better use of Django's model validation capabilities [https://docs.djangoproject.com/en/2.0/ref/models/instances/]
* Utilize Django's password upgrading and validation schemes [https://docs.djangoproject.com/en/2.0/topics/auth/passwords/]
* Enhance security, e.g. enable HTTPS/SSL protection, enable Host header validation, etc. [https://docs.djangoproject.com/en/2.0/topics/security/]
...@@ -3,7 +3,7 @@ import os ...@@ -3,7 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myprj.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "slub.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:
......
""" """
Django settings for myprj project. Django settings for slub project.
Generated by 'django-admin startproject' using Django 2.0.4. Generated by 'django-admin startproject' using Django 2.0.4.
...@@ -50,7 +50,7 @@ MIDDLEWARE = [ ...@@ -50,7 +50,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
ROOT_URLCONF = 'myprj.urls' ROOT_URLCONF = 'slub.urls'
TEMPLATES = [ TEMPLATES = [
{ {
...@@ -68,7 +68,7 @@ TEMPLATES = [ ...@@ -68,7 +68,7 @@ TEMPLATES = [
}, },
] ]
WSGI_APPLICATION = 'myprj.wsgi.application' WSGI_APPLICATION = 'slub.wsgi.application'
# Authentication # Authentication
......
"""myprj URL Configuration """slub URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/ https://docs.djangoproject.com/en/2.0/topics/http/urls/
......
""" """
WSGI config for myprj project. WSGI config for slub project.
It exposes the WSGI callable as a module-level variable named ``application``. It exposes the WSGI callable as a module-level variable named ``application``.
...@@ -11,6 +11,6 @@ import os ...@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myprj.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "slub.settings")
application = get_wsgi_application() application = get_wsgi_application()
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .helper import get_all_platform_names_from_DB from .helpers import get_all_platform_names_from_DB
from .models import Platform from .models import Platform
# Create your validators here. # Create your validators here.
......
...@@ -4,6 +4,7 @@ from django.contrib.auth.decorators import login_required ...@@ -4,6 +4,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
...@@ -12,7 +13,7 @@ from django.utils import timezone ...@@ -12,7 +13,7 @@ from django.utils import timezone
from .auth import login, PLATFORM_SESSION_KEY from .auth import login, PLATFORM_SESSION_KEY
from .decorators import Admin_login_required, User_login_required from .decorators import Admin_login_required, User_login_required
from .forms import LoginForm, UserCredentialsImportForm, UserProfileEditForm, UserProfileRecoveryForm, UserReportCreationForm from .forms import LoginForm, UserCredentialsImportForm, UserProfileEditForm, UserProfileRecoveryForm, UserReportCreationForm
from .helper import ( from .helpers import (
get_all_platform_names_from_DB, get_form_error_messages, seq_to_aligned_str, _display_user_profile_edit_error_messages, get_all_platform_names_from_DB, get_form_error_messages, seq_to_aligned_str, _display_user_profile_edit_error_messages,
_display_user_profile_recovery_error_messages, _display_user_profile_recovery_success_message, _update_user_profile _display_user_profile_recovery_error_messages, _display_user_profile_recovery_success_message, _update_user_profile
) )
...@@ -23,6 +24,7 @@ from .models import Platform, Registry, User ...@@ -23,6 +24,7 @@ from .models import Platform, Registry, User
# For more information on configuring and using the logging system, see https://docs.djangoproject.com/en/2.0/topics/logging/ and the "Logging" part of settings.py . # For more information on configuring and using the logging system, see https://docs.djangoproject.com/en/2.0/topics/logging/ and the "Logging" part of settings.py .
# For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/, auth.py, backends.py and AuthenticationMiddleware of middleware.py . # For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/, auth.py, backends.py and AuthenticationMiddleware of middleware.py .
# For more information on managing database transactions, see https://docs.djangoproject.com/en/2.0/topics/db/transactions/ .
# For more information on making database queries, i.e. creating, retrieving, updating and deleting model instances, see https://docs.djangoproject.com/en/2.0/topics/db/queries/ . # For more information on making database queries, i.e. creating, retrieving, updating and deleting model instances, see https://docs.djangoproject.com/en/2.0/topics/db/queries/ .
# For more information on working with forms, see https://docs.djangoproject.com/en/2.0/topics/forms/ . # For more information on working with forms, see https://docs.djangoproject.com/en/2.0/topics/forms/ .
# For more information on handling file uploads, see https://docs.djangoproject.com/en/2.0/topics/http/file-uploads/ . # For more information on handling file uploads, see https://docs.djangoproject.com/en/2.0/topics/http/file-uploads/ .
...@@ -113,6 +115,7 @@ def display_default_user_profile_edit_page(request): ...@@ -113,6 +115,7 @@ def display_default_user_profile_edit_page(request):
return TemplateResponse(request, 'user_profile_edit.html', {}) return TemplateResponse(request, 'user_profile_edit.html', {})
@User_login_required @User_login_required
@transaction.atomic
def edit_user_profile(request): def edit_user_profile(request):
""" """
Collect the data, i.e. first name, last name, ece_id and email, POSTed via the user profile edit form and validate them. If they Collect the data, i.e. first name, last name, ece_id and email, POSTed via the user profile edit form and validate them. If they
...@@ -126,7 +129,7 @@ def edit_user_profile(request): ...@@ -126,7 +129,7 @@ def edit_user_profile(request):
function with the (name) list of all the changed form fields as the changed_data argument and the dictionary of all the validated function with the (name) list of all the changed form fields as the changed_data argument and the dictionary of all the validated
form data (the field names are used as keys and the corresponding validated values as values) as the cleaned_data argument. form data (the field names are used as keys and the corresponding validated values as values) as the cleaned_data argument.
""" """
session_user = request.user session_user = User.objects.select_for_update().get(pk = request.user.id)
form = UserProfileEditForm(request.POST, initial = {'first_name': session_user.first_name, 'last_name': session_user.last_name, form = UserProfileEditForm(request.POST, initial = {'first_name': session_user.first_name, 'last_name': session_user.last_name,
'ece_id': session_user.ece_id, 'email': session_user.email}) 'ece_id': session_user.ece_id, 'email': session_user.email})
if form.is_valid(): if form.is_valid():
...@@ -396,6 +399,7 @@ def search_for_recovery_user_profile(request): ...@@ -396,6 +399,7 @@ def search_for_recovery_user_profile(request):
return TemplateResponse(request, 'user_profile_recovery.html', {'error_messages': get_form_error_messages(form)}) return TemplateResponse(request, 'user_profile_recovery.html', {'error_messages': get_form_error_messages(form)})
@User_login_required @User_login_required
@transaction.atomic
def recover_user_profile(request): def recover_user_profile(request):
""" """
Check if the session user's profile in usermergeDB is empty and if the recov_user_id value exists in the session data, i.e. if a Check if the session user's profile in usermergeDB is empty and if the recov_user_id value exists in the session data, i.e. if a
...@@ -421,7 +425,7 @@ def recover_user_profile(request): ...@@ -421,7 +425,7 @@ def recover_user_profile(request):
else: else:
recov_user_id = request.session['recov_user_id'] recov_user_id = request.session['recov_user_id']
login_platform_name = request.session[PLATFORM_SESSION_KEY] login_platform_name = request.session[PLATFORM_SESSION_KEY]
session_registry = Registry.objects.get(user = session_user, platform__name = login_platform_name) session_registry = Registry.objects.select_for_update().get(user = session_user, platform__name = login_platform_name)
prev_recov_username = '' prev_recov_username = ''
# The changes in usermergeDB should be made in the following order: # The changes in usermergeDB should be made in the following order:
# * Due to the user-platform unique key constraint of the Registry model, the recovery registry should be deleted if it exists # * Due to the user-platform unique key constraint of the Registry model, the recovery registry should be deleted if it exists
...@@ -467,6 +471,7 @@ def display_default_user_credentials_import_page(request): ...@@ -467,6 +471,7 @@ def display_default_user_credentials_import_page(request):
return TemplateResponse(request, 'user_credentials_import.html', {'platform_names': get_all_platform_names_from_DB()}) return TemplateResponse(request, 'user_credentials_import.html', {'platform_names': get_all_platform_names_from_DB()})
@Admin_login_required @Admin_login_required
@transaction.atomic
def import_user_credentials(request): def import_user_credentials(request):
""" """
Collect the data, i.e. platform and credentials file, POSTed via the user credentials import form and validate them. If they are not Collect the data, i.e. platform and credentials file, POSTed via the user credentials import form and validate them. If they are not
......
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