Finished to comment everything 🎉

master
Corentin LEMAIRE 6 months ago
parent 0f9bd19bb1
commit 0c9acc7f60

@ -108,4 +108,23 @@ If you see any bugs, please tell me. I really tried my best by testing every fea
## Django model
I'm using django model a little in my code only to create me some "barriers". this standardize my objects from redis to avoid lot's of bugs. This is only kinda sort of structure to format my data.
I'm using django model a little in my code only to create me some "barriers". this standardize my objects from redis to avoid lot's of bugs. This is only kinda sort of structure to format my data.
## Pub/Sub
To implement the Redis pub/sub in Django, I decided to use this mechanism:
- The `notifications_view` is firstly displayed
- This view calls the `notifications` function thanks to HTML and JS
- This calls the get_message function, waiting for a new message
- While waiting this message, the HTMl refreshs the page every 10s to avoid the timeout
- When a message is received, `notifications` function calls back `notifications_view` and this repeats.
It stops when the user change page. So, the page is constantly "loading" waiting for a message. This allowed me to get real-time message. It's not the most efficient
way to do this but I really didn't want to use websockets or JS libraries to do this. It seems the fastest easiest way to do it.
Also, I saw in some docs we could perform some async functions in Django, but seems kinda long and heavy to implement, so I choose only this method.

@ -269,10 +269,10 @@ def refresh_expire(course_id):
def getCoursesFromPerson(person_id):
# Getting all of courses keys (not into person object because even if we use redis, which is a NoSQL technology, I really wanted to
# stay the most consistent I could by not having two lists for the same data. For example, the courses has a students list. If the students would have
# a courses list, the data would have been duplicated, for no reason except the performance. I decided not to take the risk to duplicate this data
# a courses list, the data would have been duplicated, for no reason except the efficience. I decided not to take the risk to duplicate this data
# because I strongly believe that it will stay optimized enough to iterate over all courses. Also, I decided to put the list for the courses because
# generally there will be more students than courses (because for one course, there's around 30 students), so iterate over the persons (students) would have
# been less performant than over courses. This is only a choiceof implementation. I could obviously be wrong. But that's my choice, and of course this choice
# been less efficient than over courses. This is only a choiceof implementation. I could obviously be wrong. But that's my choice, and of course this choice
# could change at the same time as my project lives)
course_keys = redis_connection.keys(f"course:*")
courses = []

@ -1,4 +1,7 @@
# To render my HTML with arguments
from django.shortcuts import render
# It's faster to return some 404 when an object does not exist
from django.http import Http404
# This corresponds to all CRUD and Utils operation associating with redis. This has to be in a different file to separate view logic and CRUD logic.
@ -7,266 +10,551 @@ from redis_app.utils.model import *
# 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
# Each function here is associated to an URL as we defined it in urls.py. They take an HttpRequest as parameter and return an HttpRequest.
# Home page, giving the logged in person
def home(request):
# Getting the logged in person
person = request.session.get("person", "")
return render(request, 'home.html', {'person': person})
# Detaisl of a course. Taking a course_id (by parameter of the route) and may take an error message and/or a success message to allow other function to call it easily
def details(request, course_id, error_message='', success_message=''):
# Getting the logged in person
person = request.session.get("person", "")
# If the course id expired, 404.
if isExpired(course_id):
raise Http404("Course has expired... Get faster next time!")
# Getting the course
course = getCourse(course_id)
if not course:
# Does not exist. 404.
raise Http404("Course does not exist")
# Setting up the course id into the course manually
course['id'] = course_id
# Getting the teacher of the course
teacher = getPerson(course['teacher'])
if not teacher:
# Does not exists. 404.
raise Http404("Teacher does not exist")
# Getting the teacher's name to display it into the view
course['teacher_name'] = teacher['name']
# Giving the bool to know if the logged in person is registered in this course
register = isPersonRegisteredToCourse(course, person)
# Geving the bool to know if the course is full
full = isCourseFull(course)
# Rendering the HTML with all these information
return render(request, 'details.html', {'course': course, 'person': person, 'register': register, 'full': full, 'error_message': error_message, 'success_message': success_message})
# Called after filled the register form
def register(request):
# Only handling POST request
if request.method == 'POST':
# Trying to register
person = register_redis(request.POST['name'], request.POST['role'])
if not person:
# Already exists if we have a false
return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "This user already exists!"})
# Successfully registered
# Setting up the person to the session
request.session["person"] = person
# Go to home
return home(request)
# Not a POST. Try again buddy.
return render(request, 'register.html', {'person': Person(name="", role=""), "error_message": "The form has been wrongly filled!"})
# Called after filled the login form
def login(request):
# Only handling POST request
if request.method == 'POST':
# Trying to login
person = login_redis(request.POST["name"], request.POST["role"])
if not person:
# The user does not exist if we have a false
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "No user found!"})
# Successfully logged in
# Setting up the person to the session
request.session["person"] = person
# Go to home
return home(request)
# Not a POST. Try again buddy.
return login_form(request)
# Displaying the register form
def register_form(request):
return render(request, 'register.html', {'person': Person(name="", role="")})
# Displaying the login form
def login_form(request):
return render(request, 'login.html', {'person': Person(name="", role="")})
# Go back to login and remove the login person from the session
def logout(request):
request.session["person"] = ""
return render(request, 'login.html', {'person': Person(name="", role="")})
# Displaying the courses page of a person
def courses(request, error_message="", success_message=""):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Getting up the person id (only the id part without person:)
person_id = getPersonId(person).split(":")[1]
# Getting all courses of the person (if he's a teacher, taking all courses where he's teacher ; if he's a student, taking all courses where he's registered to)
courses = getCoursesFromPerson(person_id)
return render(request, 'courses.html', {'person': person, 'courses': courses, 'error_message': error_message, 'success_message': success_message})
# Called after filled the update profile form
def change_profile(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Only handling POST request
if request.method == 'POST':
# Getting the person id
person_id = getPersonId(person)
if not person_id:
# Not found
return render(request, 'profile.html', {'person': person, "error_message": "Internal error, person id not found!"})
# Changing the profile with new name and role requested
person = changeProfile_redis(person_id, request.POST['name'], request.POST['role'])
if not person:
# We got a false, so this username with this role is already taken.
return render(request, 'profile.html', {'person': person, "error_message": "This username with this role is already taken!"})
# Setting up the new person into the session
request.session["person"] = person
return render(request, 'profile.html', {'person': person, 'success_message': 'Your profile has been successfully changed!'})
# Not a POST. Try again buddy.
return login_form(request)
# Go to profile page
def profile(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
return render(request, 'profile.html', {'person': person})
# After clicked on the button delete of a course
def delete_course_view(request, course_id):
# Checking if the course is expired
if isExpired(course_id):
# Course is expired (or does not exist)
raise Http404("Course has expired... Get faster next time!")
# Tries to delete the course
res = delete_course(course_id)
if not res:
return courses(request)
# Didn't delete the course, internal error
return courses(request, "Internal error: Can't delete the course.")
# Publishing the delete of the course to all students registered to it (if it's their only course where they're registered to, it make go
# them back to courses to let them register to another course before getting access back to notifications page, because they're not registered to any course now)
publish(course_id, f"DELETE: Course:{course_id} has been deleted!")
return courses(request)
# Accessing to the publish message page
def publish_message(request, success_message=""):
# Getting the logged in person
person = request.session.get("person", "")
courses_fetched = getCoursesFromPerson(getPersonId(person).split(":")[1])
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Getting person id
person_id = getPersonId(person)
if not person_id:
# Person id not found, internal error
return render(request, 'publish_message.html', {'person': person, 'courses': [], 'error_message': 'Internal error: Person id not found.'})
# Getting courses from person (by spliting it to only get the id part, withot person:)
courses_fetched = getCoursesFromPerson(person_id.split(":")[1])
# For each course, we add manually the id to it (without course:, by spliting it)
for course in courses_fetched:
course['id'] = getCourseId(course).split(':')[1]
# If courses_fetched is empty, can't publish a message because a message published is associated to a course, so he must create a course before publishing a message
if not courses_fetched:
# No course
return courses(request, 'You don\'t have any course. You must create one to publish a message!')
# Retrieved all of his courses
return render(request, 'publish_message.html', {'person': person, 'courses': courses_fetched, 'success_message': success_message})
# Called after filled the publish message form
def publish_message_form(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Only for teacher.
if person['role'] == 'Student':
return render(request, 'publish_message.html', {'person': person, "error_message": "The form has been wrongly filled!"})
# Only handling POST request
if request.method == 'POST':
# Getting information from the form
course_id = request.POST['course_id']
message = request.POST['message']
# Getting the course id
course = getCourse(course_id)
if not course:
# Course not found. 404.
raise Http404("Course not found")
# Publishing the message into the course_id channel
publish(course_id, message)
return publish_message(request, 'The message has been successfully sent!')
# Not a POST. Try again buddy
return render(request, 'publish_message.html', {'person': person, "error_message": "The form has been wrongly filled!"})
# Displaying the create course form
def create_course_view(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
return render(request, 'create.html', {'person': person})
# Displaying the update course form
def update_course_view(request, course_id):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Checking if the course is expired
if isExpired(course_id):
raise Http404("Course has expired... Get faster next time!")
# Getting the course
course = getCourse(course_id)
if not course:
# Course not found. 404.
raise Http404("Course does not exist")
# Getting course id
course_id = getCourseId(course)
if course_id == False:
# Course id not found. 404.
raise Http404("Course does not exist")
# Setting up manually the course id into the course
course['id'] = course_id.split(":")[1]
return render(request, 'update.html', {'person': person, 'course': course})
# Called after filled the create course form
def create_course_form(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Only handling POST request
if request.method == 'POST':
# Getting information from the form
course_title = request.POST['title']
course_summary = request.POST['summary']
course_level = request.POST['level']
course_places = request.POST['places']
course_teacher = getPersonId(person).split(":")[1]
# Getting the person id
person_id = getPersonId(person)
if not person_id:
# Person id not found. 404.
raise Http404("Person id not found")
# Course teacher is the person id without person:
course_teacher = person_id.split(":")[1]
# Creating the course with all these information
create_course(course_title, course_summary, course_level, course_places, course_teacher)
return courses(request, "", "The course has been successfully created!")
# Not a POST. Try again buddy;
return render(request, 'create.html', {'person': person, "error_message": "The form has been wrongly filled!"})
# Called after filled the update course form
def update_course_form(request, course_id):
# Getting the logged in person
person = request.session.get("person", "")
if isExpired(course_id):
raise Http404("Course has expired... Get faster next time!")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Checking if the course is expired
if isExpired(course_id):
raise Http404("Course has expired... Get faster next time!")
# Only handling POST request
if request.method == 'POST':
# Retriving the POST information
course_title = request.POST['title']
course_summary = request.POST['summary']
course_level = request.POST['level']
course_places = request.POST['places']
course_teacher = getPersonId(person).split(":")[1]
# Getting the person id
person_id = getPersonId(person)
if not person_id:
# Person id not found. 404.
raise Http404("Person id not found")
# Course teacher is the person id without person:
course_teacher = person_id.split(":")[1]
# Getting the course from the course id
course = getCourse(course_id)
if not course:
# Course not found. 404.
raise Http404("Course id not found")
# Setting up manually the course id
course["id"] = course_id
# Getting all course students (and "" by default)
course_students = course.get("students", "")
# Converting a string to int, very dangerous so try catch
try:
# Checking if the new number of places is not < number of students currently registered
if course_students and len(course_students.split(',')) >= int(course_places):
# Too many students registered to change the number of places that low
return render(request, 'update.html', {'person': person, "error_message": "There's too many students registered to decrease the number of places that much!", "course": course})
except:
# Number of places not an int
return render(request, 'update.html', {'person': person, "error_message": "The number of places has not the right format!", "course": course})
# Updates the course
update_course(course_id, course_title, course_summary, course_level, course_places, course_teacher)
return courses(request, "", "The course has been successfully changed!")
# Not a POST. Try again buddy;
return render(request, 'update.html', {'person': person, "error_message": "The form has been wrongly filled!", "course": course})
# When clicked on register to a course
def course_register_view(request, course_id):
# Getting the logged in person
person = request.session.get("person", "")
if person == '':
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Checking if the course is expired
if isExpired(course_id):
# Course expired. 404.
raise Http404("Course has expired... Get faster next time!")
# Getting the person id
person_id = getPersonId(person).split(":")[1]
if not person_id:
# Person id not found. 404.
raise Http404("Person not found")
# Getting the course
course = getCourse(course_id)
if not course:
# Course not found. 404.
raise Http404("Course not found")
# Setting up manually the course id
course["id"] = course_id
# Getting the whole teacher object of the course teacher
teacher = getPerson(course['teacher'])
if not teacher:
# Teacher not found. 404.
raise Http404("Teacher does not exist")
# Setting up manually the teacher name to the course
course['teacher_name'] = teacher['name']
# Trying to register the person to the course
if course_register(course_id, person_id):
# Registered successfully
return details(request, course_id, '', 'Successfully registered to the course.')
# Couldn't register
return details(request, course_id, "Could not register to the course. Try again later.")
# When clicked on unregister from a course
def course_unregister_view(request, course_id):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Checking if the course is expired
if isExpired(course_id):
# Course expired. 404.
raise Http404("Course has expired... Get faster next time!")
# Getting the person id
person_id = getPersonId(person).split(":")[1]
if not person_id:
# Person id not found. 404.
raise Http404("Person not found")
# Getting the course
course = getCourse(course_id)
if not course:
# Course not found. 404.
raise Http404("Course not found")
# Setting up manually the course id
course["id"] = course_id
# Getting the whole teacher object of the course teacher
teacher = getPerson(course['teacher'])
if not teacher:
# Teacher not found. 404.
raise Http404("Teacher does not exist")
# Setting up manually the teacher name to the course
course['teacher_name'] = teacher['name']
# Trying to unregister the person from the course
if course_unregister(course_id, person_id):
# Unregistered successfully
return details(request, course_id, '', "Successfully unregistered to the course.")
# Couldn't unregister
return details(request, course_id, "Could not unregister to the course. Try again later.")
def search(keywords):
return search_redis(keywords)
# Displaying the search page
def search_view(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
return render(request, 'search.html', {'person': person})
# Called after filled the search form
def search_form(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Only handling POST request
if request.method == 'POST':
# Retrieving information
keywords = request.POST['keywords']
# All courses objects
courses = []
courses_id = search(keywords)
# Getting the courses id of the search
courses_id = search_redis(keywords)
# Getting the whole course object with their id
for course_id in courses_id:
# Getting the course with the id (splitted not to get course:)
course = getCourse(course_id.split(':')[1])
if not course:
# Didn't work, continuing not to break the feature
continue
# Setting up manually the id to the course object
course['id'] = course_id.split(":")[1]
# Adding the course to the courses list
courses.append(course)
return render(request, 'search.html', {'person': person, 'courses': courses})
# Not a POST. Try again buddy.
return render(request, 'search.html', {'person': person, "error_message": "The form has been wrongly filled!"})
# Displaying the notification view
def notifications_view(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Getting the messages received to display them
messages = request.session.get("messages", [])
return render(request, 'notifications.html', {'person': person, 'messages': messages})
# This function is instantly called after notifications_view. How does my subscribe system work? Please referr to the README to understand it (cf. Section Pub/sub).
def notifications(request):
# Getting the logged in person
person = request.session.get("person", "")
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Getting the messages from the session not to lose them when navigating
messages = request.session.get("messages", [])
if person == "":
# The user is not logged in. Go to login
return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"})
# Getting the person id
person_id = getPersonId(person)
if not person_id:
# Person id not found.
return render(request, 'notifications.html', {'person': person, 'messages': messages, 'error_message': 'Internale error: person id not found.'})
# Getting the courses id where the person is registered to.
courses_id = [course['id'] for course in getCoursesFromPerson(person_id.split(":")[1])]
if not courses_id:
# When the person is not registered to any course, ask him to register to some course by redirecting him to courses page
# to make him know he's not registered to any course
return courses(request, "You're not registered to any course. Please register to one to follow notifications.")
# Waiting for the message, for a new notification, subscribe to all of his courses channel.
message = get_message(*courses_id)
messages.append(message)
messages.reverse()
# When a message is received, adding it to the messages list at the beginning to get the latest message at the top of the page, and the oldest at the bottom
messages.insert(0, message)
# Updating the new messages list to the session to display it to the notifications view.
request.session["message"] = messages
return notifications_view(request)
# Clearing the notifications (the session messages)
def clear_notifications(request):
# Messages from session are now an empty list, clearing the list.
request.session["messages"] = []
# Displaying again the notification view
return notifications_view(request)
# Every id is only the number, not the course:number or person:number
Loading…
Cancel
Save