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 COURSE_CHANNEL = "course" cancel = True personLoggedIn = "" redis_connection = redis.Redis(host='localhost', port=6379, decode_responses=True) pub_sub = redis_connection.pubsub() create_index(redis_connection=redis_connection) def isExpired(course_id): global redis_connection ttl = redis_connection.ttl(f"course:{course_id}") if ttl == -2 or ttl == 0: # If -1, the key has no expiration return True return False def isCourseFull(course): students = course.get('students', '') # Because it crashes with ['students'], we have to put a default value: '' here if not students: return False try: places = int(course['places']) if len(students.split(',')) >= places: return True return False except: return False 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 getCourseId2(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 getCourseId(course): return getCourseId2(course['title'], course['teacher']) def home(request): global cancel global personLoggedIn cancel = True return render(request, 'home.html', {'person': personLoggedIn}) def isPersonRegisteredToCourse(course): global personLoggedIn global redis_connection if not personLoggedIn: return False person_id = getPersonId(personLoggedIn["name"], personLoggedIn["role"]).split(":")[1] if not person_id: return False course_id = getCourseId(course) if not course_id: return False course_keys = redis_connection.keys(f"course:*") for key in course_keys: course_data = redis_connection.hgetall(key) if personLoggedIn['role'] == "Student": if person_id in course_data.get('students', '').split(","): current_course_id = getCourseId2(course_data['title'], course_data['teacher']) if current_course_id == False: continue if course_id == current_course_id: return True return False def details(request, course_id, error_message=''): global redis_connection global cancel global personLoggedIn cancel = True if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") course = redis_connection.hgetall(f"course:{course_id}") if not course: raise Http404("Course does not exist") course['id'] = course_id teacher = redis_connection.hgetall(f"person:{course['teacher']}") if not teacher: raise Http404("Teacher does not exist") course['teacher_name'] = teacher['name'] register = isPersonRegisteredToCourse(course) full = isCourseFull(course) return render(request, 'details.html', {'course': course, 'person': personLoggedIn, 'register': register, 'full': full, 'error_message': error_message}) def register(request): global personLoggedIn global redis_connection global cancel cancel = True if request.method == 'POST': person_name = request.POST['name'] person_role = request.POST['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) 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 global cancel cancel = True if request.method == 'POST': person_name = request.POST['name'] person_role = request.POST['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) return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "No user found!"}) return login_form(request) def register_form(request): global cancel cancel = True return render(request, 'register.html', {'person': Person(name="", role="")}) def login_form(request): global cancel cancel = True return render(request, 'login.html', {'person': Person(name="", role="")}) def logout(request): global personLoggedIn global cancel cancel = True personLoggedIn = "" return render(request, 'login.html', {'person': Person(name="", role="")}) def courses(request): global personLoggedIn global cancel cancel = True 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 = getCourseId2(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 = getCourseId2(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 global cancel cancel = True if personLoggedIn == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) if request.method == 'POST': person_name = request.POST['name'] person_role = request.POST['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 global cancel cancel = True if personLoggedIn == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) return render(request, 'profile.html', {'person': personLoggedIn}) def delete_course(course_id): course = redis_connection.hgetall(f"course:{course_id}") if not course: return False redis_connection.delete(f"course:{course_id}") return True def delete_course_view(request, course_id): global personLoggedIn if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") res = delete_course(course_id) if not res: return courses(request) return courses(request) def create_course(course_title, course_summary, course_level, course_places, course_teacher): course_id = uuid.uuid4() redis_connection.hset(f"course:{course_id}", mapping={ "title": course_title, "summary": course_summary, "level": course_level, "places": course_places, "teacher": course_teacher }) redis_connection.expire(f"course:{course_id}", DEFAULT_EXPIRE_TIME) def update_course(course_id, course_title, course_summary, course_level, course_places, course_teacher): redis_connection.hset(f"course:{course_id}", mapping={ "title": course_title, "summary": course_summary, "level": course_level, "places": course_places, "teacher": course_teacher }) redis_connection.expire(f"course:{course_id}", DEFAULT_EXPIRE_TIME) def create_course_view(request): global personLoggedIn return render(request, 'create.html', {'person': personLoggedIn}) def update_course_view(request, course_id): global personLoggedIn if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") course = redis_connection.hgetall(f"course:{course_id}") if not course: raise Http404("Course does not exist") course_id = getCourseId2(course['title'], course['teacher']) if course_id == False: raise Http404("Course does not exist") course['id'] = course_id.split(":")[1] return render(request, 'update.html', {'person': personLoggedIn, 'course': course}) def create_course_form(request): global personLoggedIn global redis_connection global cancel cancel = True if personLoggedIn == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) if request.method == 'POST': course_title = request.POST['title'] course_summary = request.POST['summary'] course_level = request.POST['level'] course_places = request.POST['places'] course_teacher = getPersonId(personLoggedIn['name'], personLoggedIn['role']).split(":")[1] create_course(course_title, course_summary, course_level, course_places, course_teacher) return courses(request) return render(request, 'create.html', {'person': personLoggedIn, "error_message": "The form has been wrongly filled!"}) def update_course_form(request, course_id): global personLoggedIn global redis_connection global cancel cancel = True if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") if personLoggedIn == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) if request.method == 'POST': course_title = request.POST['title'] course_summary = request.POST['summary'] course_level = request.POST['level'] course_places = request.POST['places'] course_teacher = getPersonId(personLoggedIn['name'], personLoggedIn['role']).split(":")[1] update_course(course_id, course_title, course_summary, course_level, course_places, course_teacher) return courses(request) return render(request, 'update.html', {'person': personLoggedIn, "error_message": "The form has been wrongly filled!"}) # Test if places are not limited 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 try: places = int(course['places']) if students != '': students_nb = len(students.split(',')) if students_nb and students_nb >= places: return False except: return False if not 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 course_register_view(request, course_id): global personLoggedIn global redis_connection if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") person_id = getPersonId(personLoggedIn["name"], personLoggedIn["role"]).split(":")[1] if not person_id: raise Http404("Person not found") course = redis_connection.hgetall(f"course:{course_id}") if not course: raise Http404("Course not found") course["id"] = course_id teacher = redis_connection.hgetall(f"person:{course['teacher']}") if not teacher: raise Http404("Teacher does not exist") course['teacher_name'] = teacher['name'] if course_register(course_id, person_id): return details(request, course_id) return details(request, course_id, "Could not register to the course. Try again later.") def course_unregister_view(request, course_id): global personLoggedIn global redis_connection if personLoggedIn == "": return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") person_id = getPersonId(personLoggedIn["name"], personLoggedIn["role"]).split(":")[1] if not person_id: raise Http404("Person not found") course = redis_connection.hgetall(f"course:{course_id}") if not course: raise Http404("Course not found") course["id"] = course_id teacher = redis_connection.hgetall(f"person:{course['teacher']}") if not teacher: raise Http404("Teacher does not exist") course['teacher_name'] = teacher['name'] if course_unregister(course_id, person_id): return details(request, course_id) return details(request, course_id, "Could not unregister to the course. Try again later.") 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 def publish(course): pub_sub.publish(COURSE_CHANNEL + ":" + getCourseId(course), str(course)) # Courses is a list of coursee id to subscribe to def subscribe(courses_id): global cancel cancel = False pub_sub.psubscribe(*courses_id) for message in pub_sub.listen(): if cancel: break if message["type"] != 'pmessage': continue send_new_course_notification(message) def send_new_course_notification(message): print(message) def search(keywords): courses = [] for keyword in keywords.split(' '): if not keyword: # When the users end his keywords with a ' ' continue title_query = f"@title:{keyword}" summary_query = f"@summary:{keyword}" level_query = f"@level:{keyword}" teacher_query = f"@teacher:{keyword}" title_results = redis_connection.execute_command('FT.SEARCH', 'idx:courses', title_query) summary_results = redis_connection.execute_command('FT.SEARCH', 'idx:courses', summary_query) level_results = redis_connection.execute_command('FT.SEARCH', 'idx:courses', level_query) teacher_results = redis_connection.execute_command('FT.SEARCH', 'idx:courses', teacher_query) for i in range(1, len(title_results), 2): courses.append(title_results[i]) for i in range(1, len(summary_results), 2): courses.append(summary_results[i]) for i in range(1, len(level_results), 2): courses.append(level_results[i]) for i in range(1, len(teacher_results), 2): courses.append(teacher_results[i]) return courses def search_view(request): global personLoggedIn return render(request, 'search.html', {'person': personLoggedIn}) def search_form(request): global personLoggedIn global redis_connection global cancel cancel = True if request.method == 'POST': keywords = request.POST['keywords'] courses = [] courses_id = search(keywords) for course_id in courses_id: course = redis_connection.hgetall(course_id) if not course: continue course['id'] = course_id.split(":")[1] courses.append(course) return render(request, 'search.html', {'person': personLoggedIn, 'courses': courses}) return render(request, 'search.html', {'person': personLoggedIn, "error_message": "The form has been wrongly filled!"}) # Every id is only the number, not the course:number or person:number