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 = 1200 EXPIRE_REFRESH_TIME = 60 messages = [] # All messages obtained with pub/sub. Will refresh the view every time a message is received redis_connection = redis.Redis(host='localhost', port=6379, decode_responses=True) pub_sub = redis_connection.pubsub() create_index(redis_connection=redis_connection) def get_uuid4(): return str(uuid.uuid4()).replace('-', '') def getCoursesFromPerson(person_id): global redis_connection course_keys = redis_connection.keys(f"course:*") courses = [] person = redis_connection.hgetall(f"person:{person_id}") for key in course_keys: course_data = redis_connection.hgetall(key) if person['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 courses 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): person = request.session.get("person", "") return render(request, 'home.html', {'person': person}) def isPersonRegisteredToCourse(course, person): global redis_connection if not person: return False person_id = getPersonId(person["name"], person["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 person['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=''): person = request.session.get("person", "") global redis_connection 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, person) full = isCourseFull(course) return render(request, 'details.html', {'course': course, 'person': person, 'register': register, 'full': full, 'error_message': error_message}) def register(request): 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:{get_uuid4()}" redis_connection.hset(person_key, mapping={ 'name': person_name, 'role': person_role }) request.session["person"] = 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): 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] request.session["person"] = 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): 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): request.session["person"] = "" return render(request, 'login.html', {'person': Person(name="", role="")}) def courses(request): person = request.session.get("person", "") if person == "": return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) person_id = getPersonId(person["name"], person["role"]).split(":")[1] courses = getCoursesFromPerson(person_id) return render(request, 'courses.html', {'person': person, 'courses': courses}) def change_profile(request): person = request.session.get("person", "") if person == '': 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(person["name"], person["role"]) if not person_id: return render(request, 'profile.html', {'person': person, "error_message": "Internal error: No user found!"}) redis_connection.hmset(person_id, mapping={"name": person_name, "role": person_role}) person["name"] = person_name person["role"] = person_role request.session["person"] = person return render(request, 'profile.html', {'person': person}) return login_form(request) def profile(request): person = request.session.get("person", "") if person == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) return render(request, 'profile.html', {'person': person}) 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): if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") res = delete_course(course_id) if not res: return courses(request) publish(course_id, f"DELETE: Course:{course_id} has been deleted!") return courses(request) def create_course(course_title, course_summary, course_level, course_places, course_teacher): course_id = get_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) publish(course_id, f"UPDATE: Course:{course_id} is now: Title: {course_title}, Summary: {course_summary}, Level: {course_level}, Places: {course_places}, Teacher: {course_teacher}") def publish_message(request): person = request.session.get("person", "") courses_fetched = getCoursesFromPerson(getPersonId(person['name'], person['role']).split(":")[1]) for course in courses_fetched: course['id'] = getCourseId(course).split(':')[1] if not courses_fetched: return courses(request) return render(request, 'publish_message.html', {'person': person, 'courses': courses_fetched}) def publish_message_form(request): person = request.session.get("person", "") global redis_connection if person == '': return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) if person['role'] == 'Student': return render(request, 'publish_message.html', {'person': person, "error_message": "The form has been wrongly filled!"}) if request.method == 'POST': course_id = request.POST['course_id'] message = request.POST['message'] course = redis_connection.hgetall(f'course:{course_id}') if not course: raise Http404("Course not found") publish(course_id, message) return publish_message(request) return render(request, 'publish_message.html', {'person': person, "error_message": "The form has been wrongly filled!"}) def create_course_view(request): person = request.session.get("person", "") return render(request, 'create.html', {'person': person}) def update_course_view(request, course_id): person = request.session.get("person", "") 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': person, 'course': course}) def create_course_form(request): person = request.session.get("person", "") global redis_connection if person == '': 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(person['name'], person['role']).split(":")[1] create_course(course_title, course_summary, course_level, course_places, course_teacher) return courses(request) return render(request, 'create.html', {'person': person, "error_message": "The form has been wrongly filled!"}) def update_course_form(request, course_id): person = request.session.get("person", "") global redis_connection if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") if person == '': 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(person['name'], person['role']).split(":")[1] course = redis_connection.hgetall(f"course:{course_id}") course["id"] = course_id course_students = course.get("students", "") try: if course_students and len(course_students.split(',')) >= int(course_places): 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: return render(request, 'update.html', {'person': person, "error_message": "The number of places has not the right format!", "course": course}) update_course(course_id, course_title, course_summary, course_level, course_places, course_teacher) return courses(request) return render(request, 'update.html', {'person': person, "error_message": "The form has been wrongly filled!", "course": course}) # 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): person = request.session.get("person", "") global redis_connection if isExpired(course_id): raise Http404("Course has expired... Get faster next time!") person_id = getPersonId(person["name"], person["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): person = request.session.get("person", "") 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(person["name"], person["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_id, message): global redis_connection redis_connection.publish(course_id, message) # Courses is a list of course id to subscribe to def subscribe(courses_id): pub_sub.psubscribe(*courses_id) for message in pub_sub.listen(): 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): person = request.session.get("person", "") return render(request, 'search.html', {'person': person}) def search_form(request): person = request.session.get("person", "") global redis_connection 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': person, 'courses': courses}) return render(request, 'search.html', {'person': person, "error_message": "The form has been wrongly filled!"}) def notifications_view(request): person = request.session.get("person", "") global messages return render(request, 'notifications.html', {'person': person, 'messages': messages}) def notifications(request): person = request.session.get("person", "") global messages if person == "": return render(request, 'login.html', {'person': Person(name="", role=""), "error_message": "You must login!"}) pub_sub.punsubscribe() courses_id = [course['id'] for course in getCoursesFromPerson(getPersonId(person['name'], person['role']).split(":")[1])] if not courses_id: return courses(request) pub_sub.psubscribe(*courses_id) for message in pub_sub.listen(): if message["type"] != 'pmessage': continue messages.append(message) return notifications_view(request) def clear_notifications(request): global messages messages = [] return notifications_view(request) # Every id is only the number, not the course:number or person:number