From f639d779b4aa4a78f20bdb57112a4190b4eb4752 Mon Sep 17 00:00:00 2001 From: Corentin Date: Wed, 23 Oct 2024 18:22:19 +0200 Subject: [PATCH] Now, redis is used in Django :tada: --- CourseMaster/settings.py | 12 +- CourseMaster/urls.py | 2 +- README.md | 6 + redis/templates/home.html | 9 - redis/urls.py | 6 - redis/views.py | 4 - {redis => redis_app}/__init__.py | 0 redis_app/admin.py | 6 + redis_app/apps.py | 6 + {redis => redis_app}/migrations/__init__.py | 0 {redis => redis_app}/models.py | 22 +- redis_app/templates/courses.html | 12 ++ redis_app/templates/details.html | 18 ++ redis_app/templates/home.html | 13 ++ redis_app/templates/login.html | 20 ++ redis_app/templates/profile.html | 19 ++ redis_app/templates/register.html | 19 ++ redis_app/templates/test.html | 7 + {redis => redis_app}/tests.py | 0 redis_app/urls.py | 15 ++ redis_app/utils/__init__.py | 0 redis_app/utils/index.py | 23 ++ redis_app/views.py | 200 ++++++++++++++++++ test/__init__.py | 0 {redis => test}/admin.py | 0 {redis => test}/apps.py | 0 {redis => test}/migrations/0001_initial.py | 0 ...02_remove_person_courses_person_courses.py | 22 ++ .../0003_course_students_course_teacher.py | 25 +++ .../migrations/0004_remove_course_students.py | 17 ++ ...5_remove_person_courses_course_students.py | 22 ++ test/migrations/__init__.py | 0 test/models.py | 68 ++++++ test/templates/courses.html | 12 ++ test/templates/details.html | 18 ++ test/templates/home.html | 10 + test/templates/login.html | 19 ++ test/templates/profil.html | 19 ++ test/templates/register.html | 19 ++ test/templates/test.html | 7 + test/tests.py | 3 + test/urls.py | 15 ++ test/views.py | 103 +++++++++ 43 files changed, 775 insertions(+), 23 deletions(-) delete mode 100644 redis/templates/home.html delete mode 100644 redis/urls.py delete mode 100644 redis/views.py rename {redis => redis_app}/__init__.py (100%) create mode 100644 redis_app/admin.py create mode 100644 redis_app/apps.py rename {redis => redis_app}/migrations/__init__.py (100%) rename {redis => redis_app}/models.py (66%) create mode 100644 redis_app/templates/courses.html create mode 100644 redis_app/templates/details.html create mode 100644 redis_app/templates/home.html create mode 100644 redis_app/templates/login.html create mode 100644 redis_app/templates/profile.html create mode 100644 redis_app/templates/register.html create mode 100644 redis_app/templates/test.html rename {redis => redis_app}/tests.py (100%) create mode 100644 redis_app/urls.py create mode 100644 redis_app/utils/__init__.py create mode 100644 redis_app/utils/index.py create mode 100644 redis_app/views.py create mode 100644 test/__init__.py rename {redis => test}/admin.py (100%) rename {redis => test}/apps.py (100%) rename {redis => test}/migrations/0001_initial.py (100%) create mode 100644 test/migrations/0002_remove_person_courses_person_courses.py create mode 100644 test/migrations/0003_course_students_course_teacher.py create mode 100644 test/migrations/0004_remove_course_students.py create mode 100644 test/migrations/0005_remove_person_courses_course_students.py create mode 100644 test/migrations/__init__.py create mode 100644 test/models.py create mode 100644 test/templates/courses.html create mode 100644 test/templates/details.html create mode 100644 test/templates/home.html create mode 100644 test/templates/login.html create mode 100644 test/templates/profil.html create mode 100644 test/templates/register.html create mode 100644 test/templates/test.html create mode 100644 test/tests.py create mode 100644 test/urls.py create mode 100644 test/views.py diff --git a/CourseMaster/settings.py b/CourseMaster/settings.py index c53fcf0..f568ec2 100644 --- a/CourseMaster/settings.py +++ b/CourseMaster/settings.py @@ -37,7 +37,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'redis', + 'redis_app', 'debug_toolbar', ] @@ -106,6 +106,16 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://0.0.0.0:6379/", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient" + }, + } +} + # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ diff --git a/CourseMaster/urls.py b/CourseMaster/urls.py index 014ef7f..69dfc5c 100644 --- a/CourseMaster/urls.py +++ b/CourseMaster/urls.py @@ -20,6 +20,6 @@ import debug_toolbar urlpatterns = [ path('admin/', admin.site.urls), - path('redis/', include('redis.urls')), + path('redis/', include('redis_app.urls')), path('__debug__/', include(debug_toolbar.urls)) ] diff --git a/README.md b/README.md index c9b1086..a40a76f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ Now, you can install Django by doing `pipenv install django`. This will create you a virtual environment and install Django into it. +### Django-Redis installation + +Type `pip install redis` to install redis. + +To run the redis server, type `docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest` + ## Configuration In the repo folder, type `pipenv shell`. This will lead you to the venv of the project. diff --git a/redis/templates/home.html b/redis/templates/home.html deleted file mode 100644 index 4ef112a..0000000 --- a/redis/templates/home.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {% if name %} -

Welcome {{ name }}!

- {% else %} -

Welcome young padawan!

- {% endif %} - - \ No newline at end of file diff --git a/redis/urls.py b/redis/urls.py deleted file mode 100644 index 6e62d02..0000000 --- a/redis/urls.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('', views.home, name='home') -] \ No newline at end of file diff --git a/redis/views.py b/redis/views.py deleted file mode 100644 index 26b8bc9..0000000 --- a/redis/views.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.shortcuts import render - -def home(request): - return render(request, 'home.html', {'name': 'Plheyer'}) \ No newline at end of file diff --git a/redis/__init__.py b/redis_app/__init__.py similarity index 100% rename from redis/__init__.py rename to redis_app/__init__.py diff --git a/redis_app/admin.py b/redis_app/admin.py new file mode 100644 index 0000000..6f3538b --- /dev/null +++ b/redis_app/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from .models import Course, Person + +admin.site.register(Course) +admin.site.register(Person) diff --git a/redis_app/apps.py b/redis_app/apps.py new file mode 100644 index 0000000..5d03691 --- /dev/null +++ b/redis_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RedisAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'redis_app' diff --git a/redis/migrations/__init__.py b/redis_app/migrations/__init__.py similarity index 100% rename from redis/migrations/__init__.py rename to redis_app/migrations/__init__.py diff --git a/redis/models.py b/redis_app/models.py similarity index 66% rename from redis/models.py rename to redis_app/models.py index 82406b8..a609a3e 100644 --- a/redis/models.py +++ b/redis_app/models.py @@ -23,10 +23,19 @@ class Course(models.Model): summary = models.CharField(max_length=1000) level = models.CharField(choices=LEVELS_CHOICES, default=BEGINNER, max_length=12) places = models.IntegerField(default=0) + teacher = models.ForeignKey('Person', on_delete=models.CASCADE, related_name='teacher', limit_choices_to={'role': 'Teacher'}) + students = models.ManyToManyField('Person', blank=True, related_name='students', limit_choices_to={'role': 'Student'}) # Utils function def __str__(self): return self.title + " " + truncate_string(self.summary) + + def getLevel(self): + if (self.level == "B"): + return "Beginner" + if (self.level == "I"): + return "Intermediate" + return "Advanced" # Student or teacher, what differs? For teachers, courses will be the courses taught, and for students, the courses followed. # However, this only deferrs into a 'role'. If it's a teacher, then the courses are taught, else they are followed. @@ -42,9 +51,18 @@ class Person(models.Model): # Defining our attributes which equal the Course table's columns name = models.CharField(max_length=200) - courses = models.ForeignKey(Course, on_delete=models.CASCADE) role = models.CharField(choices=ROLE_CHOICES, default=STUDENT, max_length=7) + # Return the courses list for the Person + # If it's a teacher, returns all courses he teaches + # If it's a student, returns all courses he follows + @property + def courses(self): + if (self.role == "Student"): + return [course for course in Course.objects.all() if self in course.students.all()] + return Course.objects.filter(teacher=self) + # Utils function def __str__(self): - return self.name + " is a " + self.role \ No newline at end of file + return self.name + " is a " + self.role + \ No newline at end of file diff --git a/redis_app/templates/courses.html b/redis_app/templates/courses.html new file mode 100644 index 0000000..4f4b769 --- /dev/null +++ b/redis_app/templates/courses.html @@ -0,0 +1,12 @@ + + +

Courses for {{ person.name }}

+ {% if courses %} + {% for course in courses %} + {{ course.title }} + {% endfor %} + {% else %} +

No course yet

+ {% endif %} + + \ No newline at end of file diff --git a/redis_app/templates/details.html b/redis_app/templates/details.html new file mode 100644 index 0000000..760ece5 --- /dev/null +++ b/redis_app/templates/details.html @@ -0,0 +1,18 @@ + + + {% if course %} +

Welcome in the course {{ course.title }}!

+

+ {{ course.summary }} +

+

+ This course is for {{ course.getLevel }} students +

+

+ There's only {{ course.places }} places +

+ {% else %} +

No course found!

+ {% endif %} + + \ No newline at end of file diff --git a/redis_app/templates/home.html b/redis_app/templates/home.html new file mode 100644 index 0000000..83b08c4 --- /dev/null +++ b/redis_app/templates/home.html @@ -0,0 +1,13 @@ + + + {% if person != '' %} +

Welcome {{ person.name }}!

+ {% else %} +

Welcome young padawan!

+ {% endif %} + Login + Register + Courses + Profile + + \ No newline at end of file diff --git a/redis_app/templates/login.html b/redis_app/templates/login.html new file mode 100644 index 0000000..5c406fc --- /dev/null +++ b/redis_app/templates/login.html @@ -0,0 +1,20 @@ + + +
+ {% csrf_token %} +
+

Login

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ + Register +
+ + \ No newline at end of file diff --git a/redis_app/templates/profile.html b/redis_app/templates/profile.html new file mode 100644 index 0000000..6d5fa1d --- /dev/null +++ b/redis_app/templates/profile.html @@ -0,0 +1,19 @@ + + +
+ {% csrf_token %} +
+

Profile

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/redis_app/templates/register.html b/redis_app/templates/register.html new file mode 100644 index 0000000..94ee63a --- /dev/null +++ b/redis_app/templates/register.html @@ -0,0 +1,19 @@ + + +
+ {% csrf_token %} +
+

Register

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/redis_app/templates/test.html b/redis_app/templates/test.html new file mode 100644 index 0000000..d343734 --- /dev/null +++ b/redis_app/templates/test.html @@ -0,0 +1,7 @@ + + + {% for user in users %} + {{ user }} + {% endfor %} + + \ No newline at end of file diff --git a/redis/tests.py b/redis_app/tests.py similarity index 100% rename from redis/tests.py rename to redis_app/tests.py diff --git a/redis_app/urls.py b/redis_app/urls.py new file mode 100644 index 0000000..1b03e77 --- /dev/null +++ b/redis_app/urls.py @@ -0,0 +1,15 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('', views.details, name='details'), + path('register_form', views.register_form, name='register_form'), + path('register', views.register, name='register'), + path('login_form', views.login_form, name='login_form'), + path('login', views.login, name='login'), + path('courses', views.courses, name='courses'), + path('change_profile', views.change_profile, name='change_profile'), + path('profile', views.profile, name='profile'), + path('logout', views.logout, name='logout'), +] \ No newline at end of file diff --git a/redis_app/utils/__init__.py b/redis_app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/redis_app/utils/index.py b/redis_app/utils/index.py new file mode 100644 index 0000000..eaac040 --- /dev/null +++ b/redis_app/utils/index.py @@ -0,0 +1,23 @@ +# Create indexes for the search feature +def create_index(redis_connection): + # Testing if the index is already created not to throw an exception when we launch the app multiple times (and i swear it was useful when I devlopped) + # Thanks to the great RediSearch module of Redis Stack (I'll repeat the Redis doc) I can implement a search feature on every property indexed (except on students list for confidentiality of course) + # So here, I'm indexing every member of my model because I want to expose every property to let user search in every property (except on students list for confidentiality of course) + # I'm using hset/hget... because it's my favorite one + it's a fast storage data type + redis_connection.execute_command('FT.DROPINDEX', 'idx:courses') + redis_connection.execute_command('FT.CREATE', 'idx:courses', 'ON', 'HASH', 'PREFIX', '1', 'course:', + 'SCHEMA', + 'title', 'TEXT', + 'summary', 'TEXT', + 'level', 'TEXT', # Found Tag here, it's for enums, when we know the possibility + 'places', 'NUMERIC', + 'teacher', 'TEXT', # ToString of the teacher because it's a primary key + ) + + # Same for persons + redis_connection.execute_command('FT.DROPINDEX', 'idx:persons') + redis_connection.execute_command('FT.CREATE', 'idx:persons', 'ON', 'HASH', 'PREFIX', '1', 'person:', + 'SCHEMA', + 'name', 'TEXT', + 'role', 'TEXT' + ) \ No newline at end of file diff --git a/redis_app/views.py b/redis_app/views.py new file mode 100644 index 0000000..18873f7 --- /dev/null +++ b/redis_app/views.py @@ -0,0 +1,200 @@ +from django.shortcuts import render +from django.http import Http404 +import redis +import uuid +from redis_app.utils.index import create_index +# This model only helps us the development part to set rules on objects (we do not use the django model to save objects in it) +from .models import Person + +DEFAULT_EXPIRE_TIME = 120 +EXPIRE_REFRESH_TIME = 60 + +personLoggedIn = "" +redis_connection = redis.Redis(host='localhost', port=6379, decode_responses=True) +create_index(redis_connection=redis_connection) + +def home(request, person=''): + return render(request, 'home.html', {'person': person}) + +def details(request, course_id): + global redis_connection + course = redis_connection.hgetall(f"course:{course_id}") + if course is None: + raise Http404("Course does not exist") + return render(request, 'details.html', {'course': course}) + +# Handle error messages. +def register(request): + global personLoggedIn + global redis_connection + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + query = f"@name:{person_name} @role:{person_role}" + results = redis_connection.execute_command('FT.SEARCH', 'idx:persons', query) + # First element = number of results + if results[0] > 0: + return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "This user already exists!"}) + person_key = f"person:{uuid.uuid4()}" + redis_connection.hset(person_key, mapping={ + 'name': person_name, + 'role': person_role + }) + personLoggedIn = redis_connection.hgetall(person_key) + return home(request, personLoggedIn) + + return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "The form has been wrongly filled!"}) + +def login(request): + global personLoggedIn + global redis_connection + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + + query = f"@name:{person_name} @role:{person_role}" + results = redis_connection.execute_command('FT.SEARCH', 'idx:persons', query) + if results[0] > 0: + person_key = results[1] + personLoggedIn = redis_connection.hgetall(person_key) + return home(request, personLoggedIn) + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "No user found!"}) + + return login_form(request) + +def register_form(request): + return render(request, 'register.html', {'person': Person(name="", role="")}) + +def login_form(request): + return render(request, 'login.html', {'person': Person(name="", role="")}) + +def logout(request): + global personLoggedIn + personLoggedIn = "" + return render(request, 'login.html', {'person': Person(name="", role="")}) + +def courses(request): + global personLoggedIn + if personLoggedIn == "": + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + person_id = getPersonId(personLoggedIn["name"], personLoggedIn["role"]).split(":")[1] + + course_keys = redis_connection.keys(f"course:*") + courses = [] + for key in course_keys: + course_data = redis_connection.hgetall(key) + if personLoggedIn['role'] == "Student": + if person_id in course_data.get("students", "").split(","): + course_id = getCourseId(course_data['title'], course_data['teacher']) + if course_id == False: + continue + course_data['id'] = course_id.split(":")[1] + courses.append(course_data) + else: + teacher_id = course_data["teacher"] + if teacher_id == person_id: + course_id = getCourseId(course_data['title'], course_data['teacher']) + if course_id == False: + continue + course_data['id'] = course_id.split(":")[1] + courses.append(course_data) + return render(request, 'courses.html', {'person': personLoggedIn, 'courses': courses}) + +def change_profile(request): + global personLoggedIn + if personLoggedIn == '': + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + + person_id = getPersonId(personLoggedIn["name"], personLoggedIn["role"]) + if not person_id: + return render(request, 'profile.html', {'person': personLoggedIn, "error_message": "Internal error: No user found!"}) + redis_connection.hmset(person_id, mapping={"name": person_name, "role": person_role}) + + personLoggedIn["name"] = person_name + personLoggedIn["role"] = person_role + return render(request, 'profile.html', {'person': personLoggedIn}) + + return login_form(request) + +def profile(request): + global personLoggedIn + if personLoggedIn == '': + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + return render(request, 'profile.html', {'person': personLoggedIn}) + + + + + +def getPersonId(person_name, person_role): + query = f"@name:{person_name} @role:{person_role}" + results = redis_connection.execute_command('FT.SEARCH', 'idx:persons', query) + if results[0] > 0: + person_key = results[1] + return person_key + return False + +def getCourseId(course_title, course_teacher): + query = f"@title:{course_title} @teacher:{course_teacher}" + results = redis_connection.execute_command('FT.SEARCH', 'idx:courses', query) + if results[0] > 0: + course_key = results[1] + return course_key + return False + + + +def course_register(course_id, person_id): + course = redis_connection.hgetall(f"course:{course_id}") + person = redis_connection.hgetall(f"person:{person_id}") + if not course or not person: + return False + + students = course.get('students', '') + if person_id in students.split(','): + return True + + if students: + new_students = person_id + else: + new_students = students + "," + person_id + + redis_connection.hset(f"course:{course_id}", "students", new_students) + return True + +def course_unregister(course_id, person_id): + course = redis_connection.hgetall(f"course:{course_id}") + person = redis_connection.hgetall(f"person:{person_id}") + if not course or not person: + return False + + students = course.get('students', '') + if person_id not in students.split(','): + return True + + students_list = students.split(",") + if len(students_list) == 1: + new_students = "" + else: + new_students = ",".join([student for student in students_list if student != person_id]) + + redis_connection.hset(f"course:{course_id}", "students", new_students) + return True + +def refresh_expire(course_id): + course = redis_connection.hgetall(f"course:{course_id}") + if not course: + return False + ttl = redis_connection.ttl(f"course:{course_id}") + if ttl <= 0: + return False + redis_connection.expire(f"course:{course_id}", ttl + EXPIRE_REFRESH_TIME) + return True + + + +# Every id is only the number, not the course:number or person:number \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/redis/admin.py b/test/admin.py similarity index 100% rename from redis/admin.py rename to test/admin.py diff --git a/redis/apps.py b/test/apps.py similarity index 100% rename from redis/apps.py rename to test/apps.py diff --git a/redis/migrations/0001_initial.py b/test/migrations/0001_initial.py similarity index 100% rename from redis/migrations/0001_initial.py rename to test/migrations/0001_initial.py diff --git a/test/migrations/0002_remove_person_courses_person_courses.py b/test/migrations/0002_remove_person_courses_person_courses.py new file mode 100644 index 0000000..02bcd86 --- /dev/null +++ b/test/migrations/0002_remove_person_courses_person_courses.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.16 on 2024-10-19 09:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('redis', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='person', + name='courses', + ), + migrations.AddField( + model_name='person', + name='courses', + field=models.ManyToManyField(blank=True, to='redis.course'), + ), + ] diff --git a/test/migrations/0003_course_students_course_teacher.py b/test/migrations/0003_course_students_course_teacher.py new file mode 100644 index 0000000..6918906 --- /dev/null +++ b/test/migrations/0003_course_students_course_teacher.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.16 on 2024-10-19 09:22 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('redis', '0002_remove_person_courses_person_courses'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='students', + field=models.ManyToManyField(blank=True, limit_choices_to={'role': 'Student'}, related_name='students', to='redis.person'), + ), + migrations.AddField( + model_name='course', + name='teacher', + field=models.ForeignKey(default=3, limit_choices_to={'role': 'Teacher'}, on_delete=django.db.models.deletion.CASCADE, related_name='teacher', to='redis.person'), + preserve_default=False, + ), + ] diff --git a/test/migrations/0004_remove_course_students.py b/test/migrations/0004_remove_course_students.py new file mode 100644 index 0000000..84fbb05 --- /dev/null +++ b/test/migrations/0004_remove_course_students.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-10-19 09:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('redis', '0003_course_students_course_teacher'), + ] + + operations = [ + migrations.RemoveField( + model_name='course', + name='students', + ), + ] diff --git a/test/migrations/0005_remove_person_courses_course_students.py b/test/migrations/0005_remove_person_courses_course_students.py new file mode 100644 index 0000000..d50f40f --- /dev/null +++ b/test/migrations/0005_remove_person_courses_course_students.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.16 on 2024-10-19 09:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('redis', '0004_remove_course_students'), + ] + + operations = [ + migrations.RemoveField( + model_name='person', + name='courses', + ), + migrations.AddField( + model_name='course', + name='students', + field=models.ManyToManyField(blank=True, limit_choices_to={'role': 'Student'}, related_name='students', to='redis.person'), + ), + ] diff --git a/test/migrations/__init__.py b/test/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/models.py b/test/models.py new file mode 100644 index 0000000..a609a3e --- /dev/null +++ b/test/models.py @@ -0,0 +1,68 @@ +from django.db import models + +# Utils function to truncate my string when it's too long (use in to string method) +def truncate_string(text): + if len(text) > 15: + return text[:15] + "..." + else: + return text + +class Course(models.Model): + # Defining choices for the course level + BEGINNER = "B" + INTERMEDIATE = "I" + ADVANCED = "A" + LEVELS_CHOICES = [ + (BEGINNER, "Beginner"), + (INTERMEDIATE, "Intermediate"), + (ADVANCED, "Advanced"), + ] + + # Defining our attributes which equal the Course table's columns + title = models.CharField(max_length=200) + summary = models.CharField(max_length=1000) + level = models.CharField(choices=LEVELS_CHOICES, default=BEGINNER, max_length=12) + places = models.IntegerField(default=0) + teacher = models.ForeignKey('Person', on_delete=models.CASCADE, related_name='teacher', limit_choices_to={'role': 'Teacher'}) + students = models.ManyToManyField('Person', blank=True, related_name='students', limit_choices_to={'role': 'Student'}) + + # Utils function + def __str__(self): + return self.title + " " + truncate_string(self.summary) + + def getLevel(self): + if (self.level == "B"): + return "Beginner" + if (self.level == "I"): + return "Intermediate" + return "Advanced" + +# Student or teacher, what differs? For teachers, courses will be the courses taught, and for students, the courses followed. +# However, this only deferrs into a 'role'. If it's a teacher, then the courses are taught, else they are followed. +# All the logic can be simplified with the role attribute. Later, we'll also be able to check permission with this attribute. +class Person(models.Model): + # Defining choices for the course level + STUDENT = "Student" + TEACHER = "Teacher" + ROLE_CHOICES = [ + (STUDENT, "Student"), + (TEACHER, "Teacher"), + ] + + # Defining our attributes which equal the Course table's columns + name = models.CharField(max_length=200) + role = models.CharField(choices=ROLE_CHOICES, default=STUDENT, max_length=7) + + # Return the courses list for the Person + # If it's a teacher, returns all courses he teaches + # If it's a student, returns all courses he follows + @property + def courses(self): + if (self.role == "Student"): + return [course for course in Course.objects.all() if self in course.students.all()] + return Course.objects.filter(teacher=self) + + # Utils function + def __str__(self): + return self.name + " is a " + self.role + \ No newline at end of file diff --git a/test/templates/courses.html b/test/templates/courses.html new file mode 100644 index 0000000..4f4b769 --- /dev/null +++ b/test/templates/courses.html @@ -0,0 +1,12 @@ + + +

Courses for {{ person.name }}

+ {% if courses %} + {% for course in courses %} + {{ course.title }} + {% endfor %} + {% else %} +

No course yet

+ {% endif %} + + \ No newline at end of file diff --git a/test/templates/details.html b/test/templates/details.html new file mode 100644 index 0000000..760ece5 --- /dev/null +++ b/test/templates/details.html @@ -0,0 +1,18 @@ + + + {% if course %} +

Welcome in the course {{ course.title }}!

+

+ {{ course.summary }} +

+

+ This course is for {{ course.getLevel }} students +

+

+ There's only {{ course.places }} places +

+ {% else %} +

No course found!

+ {% endif %} + + \ No newline at end of file diff --git a/test/templates/home.html b/test/templates/home.html new file mode 100644 index 0000000..e2bcfe0 --- /dev/null +++ b/test/templates/home.html @@ -0,0 +1,10 @@ + + + {% if person != '' %} +

Welcome {{ person.name }}!

+ {% else %} +

Welcome young padawan!

+ {% endif %} + Courses + + \ No newline at end of file diff --git a/test/templates/login.html b/test/templates/login.html new file mode 100644 index 0000000..edea6fa --- /dev/null +++ b/test/templates/login.html @@ -0,0 +1,19 @@ + + +
+ {% csrf_token %} +
+

Login

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/test/templates/profil.html b/test/templates/profil.html new file mode 100644 index 0000000..a782417 --- /dev/null +++ b/test/templates/profil.html @@ -0,0 +1,19 @@ + + +
+ {% csrf_token %} +
+

Profil

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/test/templates/register.html b/test/templates/register.html new file mode 100644 index 0000000..94ee63a --- /dev/null +++ b/test/templates/register.html @@ -0,0 +1,19 @@ + + +
+ {% csrf_token %} +
+

Register

+ {% if error_message %}

{{ error_message }}

{% endif %} +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/test/templates/test.html b/test/templates/test.html new file mode 100644 index 0000000..d343734 --- /dev/null +++ b/test/templates/test.html @@ -0,0 +1,7 @@ + + + {% for user in users %} + {{ user }} + {% endfor %} + + \ No newline at end of file diff --git a/test/tests.py b/test/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/test/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/test/urls.py b/test/urls.py new file mode 100644 index 0000000..8f117f3 --- /dev/null +++ b/test/urls.py @@ -0,0 +1,15 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('', views.details, name='details'), + path('register_form', views.register_form, name='register_form'), + path('register', views.register, name='register'), + path('login_form', views.login_form, name='login_form'), + path('login', views.login, name='login'), + path('courses', views.courses, name='courses'), + path('change_profil', views.change_profil, name='change_profil'), + path('profil', views.profil, name='profil'), + path('cached', views.cached, name='cached'), +] \ No newline at end of file diff --git a/test/views.py b/test/views.py new file mode 100644 index 0000000..ca3c847 --- /dev/null +++ b/test/views.py @@ -0,0 +1,103 @@ +from django.shortcuts import render, get_object_or_404 +from django.contrib.auth import get_user_model, authenticate, login, logout +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.views.decorators.cache import cache_page + +from .models import Course, Person + +personLoggedIn = "" + +def home(request, person=''): + return render(request, 'home.html', {'person': person}) + +def details(request, course_id): + course = get_object_or_404(Course, pk=course_id) + return render(request, 'details.html', {'course': course}) + +# Handle error messages. +def register(request): + global personLoggedIn + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + try: + person = Person.objects.get(name=person_name, role=person_role) + return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "This user already exists!"}) + except Person.DoesNotExist: + person = Person(name=person_name, role=person_role) + person.save() + personLoggedIn = person + return home(request, personLoggedIn) + + return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "The form has been wrongly filled!"}) + +def login(request): + global personLoggedIn + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + + try: + person = Person.objects.get(name=person_name, role=person_role) + personLoggedIn = person + return home(request, personLoggedIn) + except: + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "No user found!"}) + + return login_form(request) + +def register_form(request): + return render(request, 'register.html', {'person': Person(name="", role="")}) + +def login_form(request): + return render(request, 'login.html', {'person': Person(name="", role="")}) + +def logout(request): + global personLoggedIn + personLoggedIn = "" + return render(request, 'login_form.html', {'person': Person(name="", role="")}) + +def courses(request): + global personLoggedIn + if personLoggedIn == "": + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + if personLoggedIn.role == "Student": + courses = Course.objects.filter(students=personLoggedIn) + else: + courses = Course.objects.filter(teacher=personLoggedIn) + if type(courses) == Course: + courses = [courses] + return render(request, 'courses.html', {'person': personLoggedIn, 'courses': courses}) + +def change_profil(request): + global personLoggedIn + if personLoggedIn == '': + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + + if request.method == 'POST': + person_name = request.POST.get('name') + person_role = request.POST.get('role') + + personLoggedIn.name = person_name + personLoggedIn.role = person_role + personLoggedIn.save() + return render(request, 'profil.html', {'person': personLoggedIn}) + + return login_form(request) + +def profil(request): + global personLoggedIn + if personLoggedIn == '': + return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) + return render(request, 'profil.html', {'person': personLoggedIn}) + + + + + +@cache_page(60*15) +def cached(request): + user_model = get_user_model() + all_users = user_model.objects.all() + return render(request, 'test.html', {"users": all_users}) \ No newline at end of file