Commit e6f4aa1b authored by Giorgos Kazelidis's avatar Giorgos Kazelidis

- Installed concurrent-log-handler and set up the logging system

- Loaded the time zone tables in mysql database
- Added Unicode/UTF-8 data support in usermergeDB and templates
- Replaced CLASS_SESSION_KEY with PLATFORM_SESSION_KEY in the session data as the latter indicates both user model and platform
- Corrected the session auth hash calculation method of both user models to take password (and platform, in case of User model) into account
- Enhanced template structure and functionality by using XHTML5 syntax, new HTML5 semantic elements, html5shiv.js, normalize.css, field labels, appropriate images/icons, etc. and moving internal CSS to base.css
- Made some minor changes in models (changed max_length of first_name and last_name fields) and database connection options (introduced SET_DEFAULT_STORAGE_ENGINE_TO_INNODB variable and changed SQL mode)
parent 219ab1a8
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
* Working OS: Ubuntu 16.04 LTS * Working OS: Ubuntu 16.04 LTS
* Python version: 3.5.2 (pre-installed) * Python version: 3.5.2 (pre-installed)
* Git version: 2.7.4 * Git version: 2.7.4
* pip version: 8.1.1 * pip3 version: 10.0.1
* virtualenv version: 15.2.0 * virtualenv version: 15.2.0
* Django version: 2.0.4 * Django version: 2.0.4
* MySQL server version: 5.7.22 * pytz version: 2018.4
* concurrent-log-handler version: 0.9.12
* mysqlclient version: 1.3.12
* MySQL server version: 5.7.24
## 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):
...@@ -37,13 +40,20 @@ ...@@ -37,13 +40,20 @@
      (3) navigate to the local instance:       (3) navigate to the local instance:
cd userbase cd userbase
(E) install Django and MySQL inside the local instance using virtualenv with Python 3 [sources: (E) install and configure Django and MySQL inside the local instance using virtualenv with Python 3 [sources:
      -- steps 2 and 3 of https://help.dreamhost.com/hc/en-us/articles/215317948-How-to-install-Django-using-virtualenv       -- steps 2 and 3 of https://help.dreamhost.com/hc/en-us/articles/215317948-How-to-install-Django-using-virtualenv
      -- step 3 of https://www.digitalocean.com/community/tutorials/how-to-create-a-django-app-and-connect-it-to-a-database       -- step 3 of https://www.digitalocean.com/community/tutorials/how-to-create-a-django-app-and-connect-it-to-a-database
      -- end of section http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv       -- end of section http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv
      -- https://docs.djangoproject.com/en/2.0/topics/install/
      -- https://pypi.org/project/mysqlclient/
      -- https://pypi.org/project/concurrent-log-handler/
      -- https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04       -- https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04
      -- https://dev.mysql.com/doc/refman/5.7/en/mysql-tzinfo-to-sql.html
      -- https://datawookie.netlify.com/blog/2017/08/setting-up-time-zones-in-mysql/
      -- "Create a MySQL Database" section of http://www.marinamele.com/taskbuster-django-tutorial/install-and-configure-mysql-for-django       -- "Create a MySQL Database" section of http://www.marinamele.com/taskbuster-django-tutorial/install-and-configure-mysql-for-django
      -- https://gist.github.com/hofmannsven/9164408       -- https://gist.github.com/hofmannsven/9164408
      -- https://dev.mysql.com/doc/refman/5.7/en/sql-syntax.html
      -- https://stackoverflow.com/questions/43644218/why-is-table-charset-set-to-utf8mb4-and-collation-to-utf8mb4-unicode-520-ci?rq=1
      -- https://www.tutorialspoint.com/django/django_admin_interface.htm       -- https://www.tutorialspoint.com/django/django_admin_interface.htm
      -- https://docs.djangoproject.com/en/2.0/ref/django-admin/       -- https://docs.djangoproject.com/en/2.0/ref/django-admin/
      -- https://docs.python.org/3/tutorial/interpreter.html       -- https://docs.python.org/3/tutorial/interpreter.html
...@@ -65,42 +75,53 @@ ...@@ -65,42 +75,53 @@
      (5) install appropriate versions of virtual-environment-specific packages:       (5) install appropriate versions of virtual-environment-specific packages:
pip install -r requirements.txt pip install -r requirements.txt
           ALTERNATIVELY, you could install the packages (Django and MySQL Database Connector - mysqlclient) individually by typing the following:             ALTERNATIVELY, you could install the packages (Django, mysqlclient, concurrent-log-handler and pytz - the last one is installed alongside Django) individually by typing the following:
pip install Django==2.0.4 pip install Django
pip install mysqlclient pip install mysqlclient
       (6) install MySQL server (`mysql --version` to see if it is already installed): pip install concurrent-log-handler
             * WARNING: while installing MySQL server, you will be asked to set the password for the root user.       (6) install MySQL server (`mysqld --version` to see if it is already installed):
            * ATTENTION: while installing MySQL server, you will be asked to set the password for the root user.
sudo apt-get install mysql-server sudo apt-get install mysql-server
mysql_secure_installation mysql_secure_installation
       (7) use the password that was set for the root user during MySQL server installation as the 'PASSWORD' value of the 'default' dictionary of the DATABASES dictionary in **~/Desktop/userbase/myprj/myprj/settings.py**       (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**
       (8) open MySQL console with root privileges:       (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):
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
sudo service mysql restart
      (9) navigate to the project directory (myprj):
cd myprj
      (10) open MySQL console with root privileges:
python manage.py dbshell python manage.py dbshell
           ALTERNATIVELY, you could run the following command by providing the password that was set for the root user during MySQL server installation:             ALTERNATIVELY, you could run the following command by providing the password that was set for the MySQL root user:
mysql -u root -p mysql -u root -p
       (9) if the project database (usermergeDB) has already been created (`SHOW DATABASES;` to check), delete it:       (11) if the project database (usermergeDB) has already been created (`SHOW DATABASES;` to check), delete it:
DROP DATABASE usermergeDB; DROP DATABASE usermergeDB;
       (10) create the project database:       (12) create the project database (no tables are created during this substep) with "utf8mb4" as the character set and X as the collation, where X is "utf8mb4_0900_ci_ai" for MySQL (server version) 8.0, "utf8mb4_unicode_520_ci" for MySQL 5.6/5.7 or "utf8mb4_unicode_ci" for MySQL 5.5:
CREATE DATABASE usermergeDB; CREATE DATABASE usermergeDB CHARACTER SET utf8mb4 COLLATE X;
       (11) exit MySQL console:       (13) exit MySQL console:
EXIT; EXIT;
       (12) initiate the database:       (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**
      (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**
      (16) initiate the project database (the tables are created during this substep):
python manage.py migrate python manage.py migrate
       (13) open Python interpreter in interactive mode:       (17) if you have previously set SET_DEFAULT_STORAGE_ENGINE_TO_INNODB to "True", set it back to "False"
      (18) open Python interpreter in interactive mode:
python manage.py shell python manage.py shell
       (14) populate the database with test data, i.e. execute populateDB.py script, by typing the following line and pressing `Enter` TWICE:       (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('./populateDB.py') as popDB: exec(popDB.read()) with open('./usermerge/populateDB.py') as popDB: exec(popDB.read())
       (15) close Python interpreter by pressing `Ctrl-D`       (20) close Python interpreter by pressing `Ctrl-D`
       (16) deactivate the virtual environment:       (21) deactivate the virtual environment:
deactivate deactivate
...@@ -109,7 +130,7 @@ ...@@ -109,7 +130,7 @@
cd ~/Desktop/userbase cd ~/Desktop/userbase
source venv/bin/activate source venv/bin/activate
(B) navigate to the project directory (myprj): (B) navigate to the project directory:
cd myprj cd myprj
(C\) start the Django development server (at http://127.0.0.1:8000/): (C\) start the Django development server (at http://127.0.0.1:8000/):
...@@ -119,14 +140,14 @@ ...@@ -119,14 +140,14 @@
## STOPPING THE PROJECT (AND EXITING THE VIRTUAL ENVIRONMENT) ## STOPPING THE PROJECT (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/):
       -- return to the terminal where you have run `python manage.py runserver` and press `Ctrl-C`       -- return to the terminal where you have run `python manage.py runserver` and press `Ctrl-C`
(B) deactivate the virtual environment: (B) deactivate the virtual environment:
deactivate deactivate
## UPDATING THE PROJECT ## UPDATING THE PROJECT
Provided you have already downloaded the (source code of the) project (that is, you have followed the steps of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above), the recommended steps to update the (source code of the) project are: Provided you have already downloaded the (source code of the) project (that is, you have followed the steps of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above), the recommended steps to update the (source code of the) project are:
       (1) delete the local instance of the remote repository (userbase) as well as the virtual environment:       (1) delete the local instance of the remote repository (userbase) as well as the virtual environment:
rm -rf ~/Desktop/userbase rm -rf ~/Desktop/userbase
       (2) follow steps (D) and (E) of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above       (2) follow steps (D) and (E) of the "DOWNLOADING THE PROJECT & SETTING UP THE ENVIRONMENT" section above
This diff is collapsed.
This diff is collapsed.
from django.contrib.auth.hashers import check_password from django.contrib.auth.hashers import check_password, make_password
from usermerge.models import Admin, Platform, Registry, User from usermerge.models import Admin, Registry, User
# Create your backends here. # Create your backends here.
# https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#other-authentication-sources # https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#authentication-backends
# For more information on Admin, Platform, Registry and User models, see models.py . # For more information on default/customizing user authentication and password management, see https://docs.djangoproject.com/en/2.0/topics/auth/ .
# For more information on the platform names that are provided in the drop-down list of the login form, see login.html and get_names_of_SoftLab_provided_platforms_from_DB() function of views.py . # For more information on the platform names that are provided in the drop-down list of the login form, see login.html and get_names_of_SoftLab_provided_platforms_from_DB() function of views.py .
# For more information on password management functions (e.g. check_password()), see https://docs.djangoproject.com/en/2.0/topics/auth/passwords/ .
class UserBackend: class UserBackend:
def authenticate(self, request, platform, username, password): def authenticate(self, request, platform = None, username = None, password = None):
""" """
Authenticate the credentials POSTed via the login form. Authenticate the credentials POSTed via the login form.
...@@ -26,45 +25,49 @@ class UserBackend: ...@@ -26,45 +25,49 @@ class UserBackend:
the User instance that is associated with the corresponding Registry instance is returned. the User instance that is associated with the corresponding Registry instance is returned.
If any of the checks (finding the corresponding instance and comparing the passwords) fails, the authentication If any of the checks (finding the corresponding instance and comparing the passwords) fails, the authentication
fails and an empty instance (None) is returned (if the first check fails, the second is never performed). fails and an empty instance (None) is implicitly returned (if the first check fails, the second is never performed).
""" """
if platform != 'SLUB': if platform == 'SLUB':
# User authentication
platform_instance = Platform.objects.get(name = platform)
try:
registry = Registry.objects.get(platform = platform_instance, username = username)
except Registry.DoesNotExist:
return None
else:
if check_password(password, registry.password):
return registry.user
else:
return None
else:
# Admin authentication # Admin authentication
try: try:
admin = Admin.objects.get(username = username) admin = Admin.objects.get(username = username)
except Admin.DoesNotExist: except Admin.DoesNotExist:
return None # Run the default password hasher once to reduce the timing difference between an existent and
# a non-existent Admin instance (#20760), as done in ModelBackend's authenticate() method
# (https://github.com/django/django/blob/master/django/contrib/auth/backends.py).
dummy_password = make_password(password)
else: else:
if check_password(password, admin.password): if check_password(password, admin.password):
return admin return admin
else: else:
return None # User authentication
try:
registry = Registry.objects.get(platform__name = platform, username = username)
except Registry.DoesNotExist:
# Run the default password hasher once to reduce the timing difference between an existent and
# a non-existent User instance (#20760), as done in ModelBackend's authenticate() method
# (https://github.com/django/django/blob/master/django/contrib/auth/backends.py).
dummy_password = make_password(password)
else:
if check_password(password, registry.password):
return registry.user
def get_user(self, user_class, user_id): def get_user(self, user_model_label, user_id):
""" """
Return the user instance associated with the given class/model (User/Admin) and id. Return the user instance associated with the given model and id (if model is neither User nor Admin, raise a ValueError exception).
If no associated user instance is found, return an empty instance (None). If no user is retrieved, return an empty instance (None).
""" """
if user_class == 'usermerge.User': if user_model_label == 'usermerge.User':
try: try:
return User.objects.get(pk = user_id) return User.objects.get(pk = user_id)
except User.DoesNotExist: except User.DoesNotExist:
return None return None
else: elif user_model_label == 'usermerge.Admin':
try: try:
return Admin.objects.get(pk = user_id) return Admin.objects.get(pk = user_id)
except Admin.DoesNotExist: except Admin.DoesNotExist:
return None return None
else:
raise ValueError('The user_model_label argument of get_user() backend method '
'should either be "usermerge.User" or "usermerge.Admin"!')
"""
Modified version of django.contrib.auth.middleware module (https://github.com/django/django/blob/master/django/contrib/auth/middleware.py).
"""
from django.conf import settings from django.conf import settings
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
...@@ -9,6 +6,8 @@ from usermerge import auth ...@@ -9,6 +6,8 @@ from usermerge import auth
# Create your middleware here. # Create your middleware here.
# https://docs.djangoproject.com/en/2.0/topics/http/middleware/ # https://docs.djangoproject.com/en/2.0/topics/http/middleware/
# AuthenticationMiddleware is a middleware component that associates the current session 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).
def get_user(request): def get_user(request):
if not hasattr(request, '_cached_user'): if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request) request._cached_user = auth.get_user(request)
...@@ -17,9 +16,7 @@ def get_user(request): ...@@ -17,9 +16,7 @@ def get_user(request):
class AuthenticationMiddleware(MiddlewareMixin): class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request): def process_request(self, request):
assert hasattr(request, 'session'), ( assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware " 'The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE%s setting to '
"to be installed. Edit your MIDDLEWARE%s setting to insert " 'insert "django.contrib.sessions.middleware.SessionMiddleware" before "usermerge.middleware.AuthenticationMiddleware".'
"'django.contrib.sessions.middleware.SessionMiddleware' before " ) % ('_CLASSES' if settings.MIDDLEWARE is None else '')
"'usermerge.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = SimpleLazyObject(lambda: get_user(request)) request.user = SimpleLazyObject(lambda: get_user(request))
# Generated by Django 2.0.4 on 2018-06-28 11:10 # Generated by Django 2.0.4 on 2019-01-24 11:33
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
...@@ -16,8 +16,8 @@ class Migration(migrations.Migration): ...@@ -16,8 +16,8 @@ class Migration(migrations.Migration):
name='Admin', name='Admin',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=100)), ('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=100)), ('last_name', models.CharField(max_length=50)),
('email', models.EmailField(max_length=254, unique=True)), ('email', models.EmailField(max_length=254, unique=True)),
('username', models.CharField(max_length=50, unique=True)), ('username', models.CharField(max_length=50, unique=True)),
('password', models.CharField(max_length=100)), ('password', models.CharField(max_length=100)),
...@@ -39,13 +39,16 @@ class Migration(migrations.Migration): ...@@ -39,13 +39,16 @@ class Migration(migrations.Migration):
('password', models.CharField(max_length=100)), ('password', models.CharField(max_length=100)),
('platform', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='usermerge.Platform')), ('platform', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='usermerge.Platform')),
], ],
options={
'verbose_name_plural': 'registries',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='User', name='User',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=100)), ('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=100)), ('last_name', models.CharField(max_length=50)),
('ece_id', models.CharField(max_length=8, null=True, unique=True)), ('ece_id', models.CharField(max_length=8, null=True, unique=True)),
('email', models.EmailField(max_length=254, null=True, unique=True)), ('email', models.EmailField(max_length=254, null=True, unique=True)),
('last_login', models.DateTimeField(null=True)), ('last_login', models.DateTimeField(null=True)),
......
...@@ -4,32 +4,32 @@ from django.utils.crypto import salted_hmac ...@@ -4,32 +4,32 @@ from django.utils.crypto import salted_hmac
# Create your models here. # Create your models here.
# https://docs.djangoproject.com/en/2.0/ref/models/ # https://docs.djangoproject.com/en/2.0/ref/models/
# Regarding the fields whose null attribute/option is set to True (WARNING: Any unique fields whose null attribute/option is undeclared, i.e. False by default, should NEVER be allowed to be empty): The empty DB value for DateTimeFields is NULL (interpreted as None in Python), thus designating the absence of value. The empty DB value for CharFields and EmailFields is the empty/zero-length string (''), which is a valid string value. For unique CharFields and EmailFields, such as ece_id and email of the User model, whose value may NOT always be declared when inserting/updating the respective DB entries/records, this could lead in integrity errors ("Duplicate entry '' for key 'ece_id'/'email'"). To avoid such errors, NULL should be used as the empty DB value of these fields (the empty string should NOT be used in this case). For more information, see https://docs.djangoproject.com/en/2.0/ref/models/fields/ . # The below defined models impose on fields only the absolutely necessary DB-related constraints. For more information on elaborate field constraints imposed outside the scope of models, e.g. validation of ece_id and email formats, see forms.py and views.py .
# first_name and last_name fields can contain greek characters. The length of each greek character is 2 bytes, while the length of each english/punctuation character is 1 byte. # Regarding the fields whose null option is set to True (any unique fields whose null option is undeclared, i.e. False by default, should NEVER be allowed to be empty): The empty DB value for DateTimeFields is NULL (interpreted as None in Python), thus designating the absence of value. The empty DB value for CharFields and EmailFields is the empty/zero-length string (''), which is a valid string value. For unique CharFields and EmailFields, such as ece_id and email of the User model, whose value may NOT always be declared when creating/updating the respective DB entries, this could lead in integrity errors ("Duplicate entry '' for key 'ece_id'/'email'"). To avoid such errors, NULL should be used as the empty DB value of these fields (the empty string should NOT be used in this case). For more information, see https://docs.djangoproject.com/en/2.0/ref/models/fields/ .
# Django creates hashed passwords using the PBKDF2 algorithm with a SHA256 hash by default (https://docs.djangoproject.com/en/2.0/topics/auth/passwords/). The length of each hashed password is 78 bytes. # Each time a user logs in, an appropriate user_logged_in signal is emitted (see login() function in auth.py) causing the last_login field of the corresponding User/Admin instance to be updated independently of the other fields. Generally, the last_login field of a user instance is non-empty when the other fields are non-empty and it is empty when the others are empty. It is also possible, though, for the last_login field of a User instance to be non-empty when all the other fields are empty (the user logs in and, although his/her profile is completely empty, he/she opts to log out without filling it out) or for the last_login field of an Admin instance to be empty when all the other fields are non-empty (the user has never logged in but his/her profile has already been filled out 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 ECE-ID format is 031YYRRR (YY: year, RRR: registration number).
# The user_logged_in signal (see login() function in auth.py) triggers the update of the last_login fields, so the latter are necessary. # 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) and should NOT be declared during the creation/update of model instances. The REMAINING fields should satisfy specific requirements (see examples in populateDB.py):
# Original source code for get_session_auth_hash() methods and is_authenticated 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/). get_session_auth_hash() methods are needed in login() and get_user() functions of auth.py, while is_authenticated attributes are needed for login_required() decorator of views.py . # * The name field of a Platform instance should NEVER be empty. Its value can be changed validly but it should NEVER become empty.
# * ALL the fields of a Registry instance should be NON-empty. The user (user_id), username and password values can be changed validly (the platform - platform_id - value should NEVER be changed) but they should NEVER become empty.
# * ALL the fields of an Admin instance should be NON-empty. Their values can be changed validly but they should NEVER become empty.
# * 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, 3-->2 and 3-->3 (empty field values can be changed to valid non-empty ones, non-empty field values can be changed validly but they should NEVER become empty).
# The password field of a user instance should NEVER contain the user's raw password. It should, instead, contain a hashed version of the latter created by make_password() function for enhanced security (see examples in populateDB.py). The aforementioned function creates the hashed password using the PBKDF2 algorithm with a SHA256 hash by default. The length of the hashed password is 78 characters. For more information on password management, see https://docs.djangoproject.com/en/2.0/topics/auth/passwords/ .
# Given that support for time zones is enabled (USE_TZ setting is True), DateTimeField values are stored in UTC format in usermergeDB and are rendered in the current time zone, that is 'Europe/Athens' by default (specified by the TIME_ZONE setting), in templates. For more information on the time zone selection logic and render format, 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 login() and get_user() functions of auth.py and login_required() decorator of views.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/).
class User(models.Model): class User(models.Model):
first_name = models.CharField(max_length = 100) first_name = models.CharField(max_length = 50)
last_name = models.CharField(max_length = 100) last_name = models.CharField(max_length = 50)
ece_id = models.CharField(max_length = 8, unique = True, null = True) ece_id = models.CharField(max_length = 8, unique = True, null = True)
email = models.EmailField(unique = True, null = True) email = models.EmailField(unique = True, null = True) # max_length = 254 by default
last_login = models.DateTimeField(null = True) last_login = models.DateTimeField(null = True)
def get_session_auth_hash(self):
"""
Return an HMAC of the email field.
"""
key_salt = 'usermerge.models.User.get_session_auth_hash'
return salted_hmac(key_salt, self.email).hexdigest()
@property @property
def is_authenticated(self): def is_authenticated(self):
""" """
Always return True. This is a way to tell if the user has been Always return True. This is a way to tell if the user has been
authenticated in views. authenticated in templates, views, etc.
""" """
return True return True
...@@ -42,29 +42,37 @@ class Registry(models.Model): ...@@ -42,29 +42,37 @@ class Registry(models.Model):
username = models.CharField(max_length = 50) username = models.CharField(max_length = 50)
password = models.CharField(max_length = 100) password = models.CharField(max_length = 100)
def get_session_auth_hash(self):
"""
Return an HMAC of the platform (name) and password fields.
"""
key_salt = 'usermerge.models.Registry.get_session_auth_hash'
return salted_hmac(key_salt, self.password + '@' + self.platform.name).hexdigest()
class Meta: class Meta:
unique_together = (('user', 'platform',), ('platform', 'username',),) unique_together = (('user', 'platform',), ('platform', 'username',),)
verbose_name_plural = 'registries'
class Admin(models.Model): class Admin(models.Model):
first_name = models.CharField(max_length = 100) first_name = models.CharField(max_length = 50)
last_name = models.CharField(max_length = 100) last_name = models.CharField(max_length = 50)
email = models.EmailField(unique = True) email = models.EmailField(unique = True) # max_length = 254 by default
username = models.CharField(max_length = 50, unique = True) username = models.CharField(max_length = 50, unique = True)
password = models.CharField(max_length = 100) password = models.CharField(max_length = 100)
last_login = models.DateTimeField(null = True) last_login = models.DateTimeField(null = True)
def get_session_auth_hash(self): def get_session_auth_hash(self):
""" """
Return an HMAC of the email field. Return an HMAC of the password field.
""" """
key_salt = 'usermerge.models.Admin.get_session_auth_hash' key_salt = 'usermerge.models.Admin.get_session_auth_hash'
return salted_hmac(key_salt, self.email).hexdigest() return salted_hmac(key_salt, self.password).hexdigest()
@property @property
def is_authenticated(self): def is_authenticated(self):
""" """
Always return True. This is a way to tell if the user has been Always return True. This is a way to tell if the user has been
authenticated in views. authenticated in templates, views, etc.
""" """
return True return True
""" """
Populate usermergeDB (see models.py) with test data to check the correctness of the usermerge application features. Populate usermergeDB (see models.py) with test data to check the correctness of the usermerge application features.
""" """
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from usermerge.models import Admin, Platform, Registry, User from usermerge.models import Admin, Platform, Registry, User
...@@ -21,9 +22,11 @@ plgrader = Platform.objects.create(name = 'PLgrader') ...@@ -21,9 +22,11 @@ plgrader = Platform.objects.create(name = 'PLgrader')
# Populate User table with test users. # Populate User table with test users.
user1 = User.objects.create(first_name = 'Γεώργιος', last_name = 'Καζελίδης', ece_id = '03199999', user1 = User.objects.create(first_name = 'Γεώργιος', last_name = 'Καζελίδης', ece_id = '03199999',
email = 'gkazelid@undergraduate.ece.ntua.gr') email = 'gkazelid@undergraduate.ece.ntua.gr')
user2 = User.objects.create() 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 Registry table with test registries.
user_hashed_password = make_password('password') user_hashed_password = make_password('password')
registry1 = Registry.objects.create(user = user1, platform = novice, username = 'pi99b999', password = user_hashed_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) 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)
* The favicon.ico was created at https://www.favicon.cc/ .
* The apple_touch_icon.png was created by converting favicon.ico to favicon.png and resizing the latter from 16x16 to 256x256 pixels at http://icoconvert.com/ .
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"
xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<defs>
</defs>
<path style="fill:#999999;" d="M7,0.7L0.2,14.1c-0.5,1,0,1.8,1.1,1.8h13.2c1.1,0,1.6-0.8,1.1-1.8L8.8,0.7C8.3-0.2,7.5-0.2,7,0.7z
M9.4,13.4c0,0.8-0.7,1.5-1.5,1.5c-0.8,0-1.5-0.7-1.5-1.5c0-0.8,0.7-1.5,1.5-1.5C8.7,11.9,9.4,12.5,9.4,13.4z M9.2,5.9v4
c0,0.5-0.5,1-1,1H7.6c-0.5,0-1-0.5-1-1v-4c0-0.6,0.5-1,1-1h0.6C8.8,4.9,9.2,5.3,9.2,5.9z"/>
</svg>
article, aside, figure, footer, header, main, nav, section {
display: block; /* https://www.w3schools.com/html/html5_browsers.asp */
}
html {
/* https://stackoverflow.com/questions/4565942/should-global-css-styles-be-set-on-the-html-element-or-the-body-element */
background-color: #F1F1FA;
}
body {
text-align: center;
}
footer, header {
font-size: 16px;
padding: 10px 0px;
}
main {
font-size: 14px;
}
h4 {
font-size: 16px;
text-decoration: underline;
}
table {
margin: 0px auto; /* https://stackoverflow.com/questions/14073188/center-align-a-html-table */
}
#copyright_and_contact_footer, #ece_ntua_header {
background-color: #82B7EF;
}
#slub_header {
background-color: #D3D6E6;
}
#session_nav {
background-color: #82B7EF;
font-size: 16px;
padding: 10px 0px;
}
* CSS tutorials:
- https://www.w3schools.com/css/default.asp
- https://developer.mozilla.org/en-US/docs/Web/CSS
- created favicon.ico at https://www.favicon.cc/
- converted favicon.ico to favicon.png at http://icoconvert.com/
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-400-bad-request-view #} {# https://docs.djangoproject.com/en/2.0/ref/views/#the-400-bad-request-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #} {# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Σφάλμα 400 (Bad Request) SLUB - Σφάλμα 400 (Bad Request)
......
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-403-http-forbidden-view #} {# https://docs.djangoproject.com/en/2.0/ref/views/#the-403-http-forbidden-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #} {# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Σφάλμα 403 (Forbidden) SLUB - Σφάλμα 403 (Forbidden)
......
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-404-page-not-found-view #} {# https://docs.djangoproject.com/en/2.0/ref/views/#the-404-page-not-found-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #} {# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Σφάλμα 404 (Not Found) SLUB - Σφάλμα 404 (Not Found)
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
{% block content %} {% block content %}
<h4>Σφάλμα 404 (Not Found)</h4> <h4>Σφάλμα 404 (Not Found)</h4>
<p>Η ζητηθείσα ιστοσελίδα "{{ request_path }}" δεν βρέθηκε στον παρόντα διακομιστή! :(</p> <p>Η ζητηθείσα ιστοσελίδα "{{ request_path }}" δεν βρέθηκε στον παρόντα διακομιστή!</p>
{% endblock %} {% endblock %}
{# https://docs.djangoproject.com/en/2.0/ref/views/#the-500-server-error-view #} {# https://docs.djangoproject.com/en/2.0/ref/views/#the-500-server-error-view #}
{# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #} {# https://docs.djangoproject.com/en/2.0/_modules/django/views/defaults/ #}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Σφάλμα 500 (Server Error) SLUB - Σφάλμα 500 (Server Error)
......
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Αρχική Σελίδα Διαχειριστή SLUB - Αρχική Σελίδα Διαχειριστή
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="bar" id="in-session-bar" style="padding-top:10px; padding-bottom:10px;">Έχετε εισέλθει επιτυχώς! :)</div> <nav id="session_nav">
Έχετε εισέλθει ως <strong>{{ user.first_name }} {{ user.last_name }}</strong>!
| <a href="{% url 'admin_home' user.id %}">Αρχική Σελίδα</a>
| <a href="{% url 'logout' %}">Έξοδος</a>
</nav>
<h4>Αρχική Σελίδα Διαχειριστή</h4> <h4>Αρχική Σελίδα Διαχειριστή</h4>
<p><a href="{% url 'logout' %}">Έξοδος</a></p> <p><a href="{% url 'logout' %}">Έξοδος</a></p>
{% endblock %} {% endblock %}
<!DOCTYPE html> <!DOCTYPE html> {# HTML5 DOCTYPE declaration #}
<html>
{# https://stackoverflow.com/questions/14649798/what-is-lang-attribute-of-the-html-tag-used-for #}
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="el" lang="el">
<head> <head>
<meta charset="utf-8" />
{# https://stackoverflow.com/questions/6771258/what-does-meta-http-equiv-x-ua-compatible-content-ie-edge-do #}
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> <title>
{% block title %} {% block title %}
SLUB SLUB - SoftLab UserBase
{% endblock %} {% endblock %}
</title> </title>
{# https://docs.djangoproject.com/en/2.0/howto/static-files/ #} {# https://docs.djangoproject.com/en/2.0/howto/static-files/ #}
{% load static %} {% load static %}
<link rel="icon" href="{% static 'usermerge/favicon.png' %}" type="image/png"/> <base href="{% static '' %}" />
{# https://en.wikipedia.org/wiki/Favicon #}
{# https://stackoverflow.com/questions/44939439/can-one-large-size-favicon-serve-all-devices-and-browsers #}
<link rel="shortcut icon" href="images/favicon.ico" />
<link rel="apple-touch-icon" href="images/apple_touch_icon.png" />
{# https://stackoverflow.com/questions/6887336/what-is-the-difference-between-normalize-css-and-reset-css #}
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/normalize.css/normalize.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/base.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> {# https://www.w3schools.com/html/html5_browsers.asp #}
<style> <!--[if lt IE 9]>
body { <script src="https://cdn.jsdelivr.net/npm/html5shiv/dist/html5shiv.min.js"></script>
background-color: #F1F1FA; <![endif]-->
}
h4 {
text-align: center;
text-decoration: underline;
}
p {
text-align: center;
}
table {
margin: 0px auto; {# https://stackoverflow.com/questions/14073188/center-align-a-html-table #}
}
.bar, .footer, .header {
text-align: center;
width: 100%;
}
#ece-ntua-header, #footer, #in-session-bar {
background-color: #82B7EF;
}
#slub-header {
background-color: #D3D6E6;
}
</style>
</head> </head>
<body> <body>
<div class="header" id="ece-ntua-header"> <header id="ece_ntua_header" style="padding:0px;">
<table> <table>
<tbody>
<tr> <tr>
<td style="width:240px;"> <td style="width:250px;">
<img src="https://courses.softlab.ntua.gr/Images/Various/pyrforos.svg" alt="Προμηθέας Πυρφόρος" style="height:120px; width:120px;"/> <img src="images/pyrforos.svg" alt="Προμηθέας Πυρφόρος" style="height:120px; width:120px;" />
</td> </td>
<td> <td>
<p><a href="https://www.ntua.gr/" target="_blank">Εθνικό Μετσόβιο Πολυτεχνείο</a></p> <p><a href="https://www.ntua.gr/" target="_blank">Εθνικό Μετσόβιο Πολυτεχνείο</a></p>
<p><a href="https://www.ece.ntua.gr/" target="_blank">Σχολή Ηλεκτρολόγων Μηχανικών και Μηχανικών Υπολογιστών</a></p> <p>
<a href="https://www.ece.ntua.gr/" target="_blank">Σχολή Ηλεκτρολόγων Μηχανικών και Μηχανικών Υπολογιστών</a>
</p>
<p><a href="http://www.cs.ntua.gr/" target="_blank">Τομέας Τεχνολογίας Πληροφορικής και Υπολογιστών</a></p> <p><a href="http://www.cs.ntua.gr/" target="_blank">Τομέας Τεχνολογίας Πληροφορικής και Υπολογιστών</a></p>
<p><a href="http://www.softlab.ntua.gr/" target="_blank">Εργαστήριο Τεχνολογίας Λογισμικού</a></p> <p><a href="http://www.softlab.ntua.gr/" target="_blank">Εργαστήριο Τεχνολογίας Λογισμικού</a></p>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </header>
<div class="header" id="slub-header"> <header id="slub_header">
<table> <table>
<tbody>
<tr> <tr>
<td style="padding-left:10px; width:240px;"> <td style="width:250px;">
<p>
<strong> <strong>
<span style="color:red;">S</span>oft<span style="color:red;">L</span>ab <span style="color:red;">S</span>oft<span style="color:red;">L</span>ab
<span style="color:red;">U</span>ser<span style="color:red;">B</span>ase <span style="color:red;">U</span>ser<span style="color:red;">B</span>ase
</strong> </strong>
</p>
</td> </td>
<td> <td style="padding:0px 10px;">
<p> Το <strong><span style="color:red;">SLUB</span></strong> είναι το σύστημα διαχείρισης και συσχέτισης <br />
Το <strong><span style="color:red;">SLUB</span></strong> είναι το σύστημα διαχείρισης και συσχέτισης <br/> λογαριασμών για τους χρήστες των πλατφορμών του SoftLab. <br />
διαπιστευτηρίων για τους χρήστες των πλατφορμών του SoftLab. <br/> Οι πλατφόρμες που διατίθενται μέσω του SoftLab είναι <br />
Οι πλατφόρμες που διατίθενται μέσω του SoftLab είναι <br/> ο <a href="http://courses.softlab.ntua.gr/" target="_blank">Novice</a>,
ο <a href="http://courses.softlab.ntua.gr/progintro/" target="_blank">Novice</a>,
o <a href="http://grader.softlab.ntua.gr/" target="_blank">Grader</a>, o <a href="http://grader.softlab.ntua.gr/" target="_blank">Grader</a>,
το <a href="https://moodle.softlab.ntua.gr/" target="_blank">Moodle</a> και το <a href="https://moodle.softlab.ntua.gr/" target="_blank">Moodle</a> και
ο <a href="http://plgrader.softlab.ntua.gr/" target="_blank">PLgrader</a>. ο <a href="http://plgrader.softlab.ntua.gr/" target="_blank">PLgrader</a>.
</p>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </header>
<main>
{% block content %} {% block content %}
<p>Αυτή η ιστοσελίδα δεν έχει ακόμα περιεχόμενο... :(</p> <p>Αυτή η ιστοσελίδα δεν έχει ακόμα περιεχόμενο!</p>
{% endblock %} {% endblock %}
</main>
<div class="footer" id="footer"> <footer id="copyright_and_contact_footer">
<table> <table>
<tbody>
<tr> <tr>
<td style="padding-right:30px; width:240px;"> <td style="width:250px;">&#xA9; {% now 'Y' %} SoftLab</td>
<p>&copy; {% now "Y" %} SoftLab</p> <td style="width:250px;"><a href="mailto:webmaster@courses.softlab.ntua.gr">Επικοινωνία με Webmaster</a></td>
</td> <td style="width:250px; padding-left:30px;">
<td style="width:240px;"> Θερμές Ευχαριστίες σε <br />
<p><a href="mailto:webmaster@courses.softlab.ntua.gr">Επικοινωνία με Webmaster</a></p> <a href="https://www.djangoproject.com/" target="_blank">Django</a>,
</td> <a href="https://www.mysql.com/" target="_blank">MySQL</a> και
<td style="padding-left:30px; width:240px;"> <a href="https://httpd.apache.org/" target="_blank">Apache</a>
<p>
Αναπτύxθηκε σε <br/>
<a href="https://www.python.org/" target="_blank">Python</a>,
<a href="https://www.djangoproject.com/" target="_blank">Django</a> και
<a href="https://www.mysql.com/" target="_blank">MySQL</a>
</p>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </footer>
</body> </body>
</html> </html>
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Είσοδος Χρήστη/Διαχειριστή SLUB - Είσοδος Χρήστη/Διαχειριστή
...@@ -7,54 +7,52 @@ ...@@ -7,54 +7,52 @@
{% block content %} {% block content %}
<h4>Είσοδος Χρήστη/Διαχειριστή</h4> <h4>Είσοδος Χρήστη/Διαχειριστή</h4>
<form name="login" style="text-align:center;" action="{% url 'submit_login' %}" method="POST"> <form id="login_form" accept-charset="utf-8" action="{% url 'submit_login' %}" method="post">
{% csrf_token %} {% csrf_token %}
<table> <table>
<tbody>
<tr> <tr>
<td style="text-align:right;">Είσοδος ως:</td> <td style="text-align:right;"><label for="platform">Είσοδος ως:</label></td>
<td style="text-align:left;"> <td style="text-align:left;">
<select name="platform"> <select name="platform" id="platform">
<option value="SLUB">διαχειριστής του SLUB</option> <option value="SLUB">διαχειριστής του SLUB</option>
{% for platform in platform_list %} {% for name in platform_names %}
<option value="{{ platform }}" {% if platform == "Novice" %}selected{% endif %}> <option value="{{ name }}" {% if name == 'Novice' %}selected="selected"{% endif %}>
χρήστης του {{ platform }} χρήστης του {{ name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="text-align:right;">Όνομα χρήστη:</td> <td style="text-align:right;"><label for="username">Όνομα χρήστη:</label></td>
<td style="text-align:left;"> <td style="text-align:left;">
{# https://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message #} <input type="text" name="username" id="username" maxlength="50" title="" required="required" />
{# https://stackoverflow.com/questions/8078388/hiding-title-tags-on-hover #}
<input type="text" name="username" maxlength="50" onmouseover="this.title='';"
oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="text-align:right;">Κωδικός πρόσβασης:</td> <td style="text-align:right;"><label for="password">Κωδικός πρόσβασης:</label></td>
<td style="text-align:left;"> <td style="text-align:left;">
{# https://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message #} <input type="password" name="password" id="password" maxlength="50" title="" required="required" />
{# https://stackoverflow.com/questions/8078388/hiding-title-tags-on-hover #}
<input type="password" name="password" maxlength="50" onmouseover="this.title='';"
oninput="setCustomValidity('');" oninvalid="setCustomValidity('Παρακαλούμε συμπληρώστε το πεδίο!');" required/>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
<br/>
<button type="submit" value="login" name="login"><strong>Είσοδος</strong></button> <br />
<input type="submit" value="Είσοδος" />
</form> </form>
{% if error_messages %} {% if error_messages %}
{% for message in error_messages %} {% for message in error_messages %}
<p style="color:red; font-size:14px;"> <p style="color:red;">
<img src="https://moodle.softlab.ntua.gr/theme/image.php/softlab/core/1522849333/i/warning" alt="Σφάλμα"/> <img src="images/warning.svg" alt="Σφάλμα:" />
<strong>{{ message | linebreaksbr }}</strong> <strong>{{ message | linebreaksbr }}</strong>
</p> </p>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<br/> <br />
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Έξοδος Χρήστη/Διαχειριστή SLUB - Έξοδος Χρήστη/Διαχειριστή
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
{% block content %} {% block content %}
<h4>Έξοδος Χρήστη/Διαχειριστή</h4> <h4>Έξοδος Χρήστη/Διαχειριστή</h4>
<p>Εξήλθατε με επιτυχία! :)</p> <p>Εξήλθατε με επιτυχία!</p>
<p><a href="{% url 'default_login' %}">Θα θέλατε μήπως να εισέλθετε ξανά;</a></p> <p><a href="{% url 'default_login' %}">Θα θέλατε μήπως να εισέλθετε ξανά;</a></p>
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends 'base.html' %}
{% block title %} {% block title %}
SLUB - Αρχική Σελίδα Χρήστη SLUB - Αρχική Σελίδα Χρήστη
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="bar" id="in-session-bar" style="padding-top:10px; padding-bottom:10px;">Έχετε εισέλθει επιτυχώς! :)</div> <nav id="session_nav">
{% if user.first_name and user.last_name %}
Έχετε εισέλθει ως <strong>{{ user.first_name }} {{ user.last_name }}</strong>!
{% else %}
Έχετε εισέλθει επιτυχώς!
{% endif %}
| <a href="{% url 'user_home' user.id %}">Αρχική Σελίδα</a>
| <a href="{% url 'logout' %}">Έξοδος</a>
</nav>
<h4>Αρχική Σελίδα Χρήστη</h4> <h4>Αρχική Σελίδα Χρήστη</h4>
<nav id="user_home_nav">
<p><a href="{% url 'logout' %}">Έξοδος</a></p> <p><a href="{% url 'logout' %}">Έξοδος</a></p>
</nav>
{% endblock %} {% endblock %}
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