From d8e9e6d614389af16dba9e2b3b1861740755f019 Mon Sep 17 00:00:00 2001 From: Aman Tahiliani Date: Tue, 26 Mar 2024 14:07:05 -0400 Subject: [PATCH 1/5] Added Filters for the File --- backend/api/admin.py | 7 +- ...ester_topic_peeruser_last_poll_and_more.py | 138 ++++++++++++++++++ ...rse_alter_file_original_author_and_more.py | 65 +++++++++ .../0004_course_number_alter_course_name.py | 27 ++++ backend/api/models.py | 54 +++++++ backend/api/serializers.py | 38 ++++- backend/api/urls.py | 36 ++++- backend/api/user_login.py | 23 +++ backend/api/views.py | 103 ++++++++++++- backend/peer_notes/settings.py | 2 +- 10 files changed, 485 insertions(+), 8 deletions(-) create mode 100644 backend/api/migrations/0002_course_professor_semester_topic_peeruser_last_poll_and_more.py create mode 100644 backend/api/migrations/0003_file_course_alter_file_original_author_and_more.py create mode 100644 backend/api/migrations/0004_course_number_alter_course_name.py diff --git a/backend/api/admin.py b/backend/api/admin.py index 40a3aba..ba698be 100644 --- a/backend/api/admin.py +++ b/backend/api/admin.py @@ -1,5 +1,10 @@ from django.contrib import admin -from api.models import PeerUser +from api.models import PeerUser, Semester, Professor, Course, Topic, File # Register your models here. admin.site.register(PeerUser) +admin.site.register(Topic) +admin.site.register(Professor) +admin.site.register(Course) +admin.site.register(File) +admin.site.register(Semester) diff --git a/backend/api/migrations/0002_course_professor_semester_topic_peeruser_last_poll_and_more.py b/backend/api/migrations/0002_course_professor_semester_topic_peeruser_last_poll_and_more.py new file mode 100644 index 0000000..dd291c2 --- /dev/null +++ b/backend/api/migrations/0002_course_professor_semester_topic_peeruser_last_poll_and_more.py @@ -0,0 +1,138 @@ +# Generated by Django 4.2.11 on 2024-03-26 04:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Course", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=20)), + ], + ), + migrations.CreateModel( + name="Professor", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name="Semester", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=20)), + ], + ), + migrations.CreateModel( + name="Topic", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ("description", models.TextField(blank=True)), + ], + ), + migrations.AddField( + model_name="peeruser", + name="last_poll", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.CreateModel( + name="File", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("filename", models.CharField(max_length=200)), + ( + "original_author", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "peer_users", + models.ManyToManyField( + related_name="shared_files", to=settings.AUTH_USER_MODEL + ), + ), + ( + "professor", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="api.professor", + ), + ), + ( + "semester", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="api.semester", + ), + ), + ( + "topic", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="api.topic", + ), + ), + ], + ), + ] diff --git a/backend/api/migrations/0003_file_course_alter_file_original_author_and_more.py b/backend/api/migrations/0003_file_course_alter_file_original_author_and_more.py new file mode 100644 index 0000000..92fcf50 --- /dev/null +++ b/backend/api/migrations/0003_file_course_alter_file_original_author_and_more.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.11 on 2024-03-26 16:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0002_course_professor_semester_topic_peeruser_last_poll_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="file", + name="course", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="files", + to="api.course", + ), + ), + migrations.AlterField( + model_name="file", + name="original_author", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="owned_files", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="file", + name="professor", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="files", + to="api.professor", + ), + ), + migrations.AlterField( + model_name="file", + name="semester", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="files", + to="api.semester", + ), + ), + migrations.AlterField( + model_name="file", + name="topic", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="files", + to="api.topic", + ), + ), + ] diff --git a/backend/api/migrations/0004_course_number_alter_course_name.py b/backend/api/migrations/0004_course_number_alter_course_name.py new file mode 100644 index 0000000..61c07c8 --- /dev/null +++ b/backend/api/migrations/0004_course_number_alter_course_name.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.11 on 2024-03-26 17:52 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0003_file_course_alter_file_original_author_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="course", + name="number", + field=models.CharField( + default=django.utils.timezone.now, max_length=20, unique=True + ), + preserve_default=False, + ), + migrations.AlterField( + model_name="course", + name="name", + field=models.CharField(blank=True, max_length=40), + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index f343655..8e5f1dc 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -5,3 +5,57 @@ from django.db import models class PeerUser(AbstractUser): ip_address = models.GenericIPAddressField(blank=True, null=True) + last_poll = models.DateTimeField(auto_now_add=True) + + +class Topic(models.Model): + name = models.CharField(max_length=100, blank=False, null=False) + description = models.TextField(blank=True) + + def __str__(self) -> str: + return self.name + + +class Professor(models.Model): + name = models.CharField(max_length=100, blank=False, null=False) + + def __str__(self) -> str: + return self.name + + +class Semester(models.Model): + name = models.CharField(max_length=20, blank=False, null=False) + + def __str__(self) -> str: + return self.name + + +class Course(models.Model): + name = models.CharField(max_length=40, blank=True) + number = models.CharField(max_length=20, blank=False, null=False, unique=True) + + def __str__(self) -> str: + return self.number + f" ({self.name})" + + +class File(models.Model): + filename = models.CharField(max_length=200, blank=False, null=False) + original_author = models.ForeignKey( + PeerUser, on_delete=models.SET_NULL, null=True, related_name="owned_files" + ) + peer_users = models.ManyToManyField(PeerUser, related_name="shared_files") + topic = models.ForeignKey( + Topic, on_delete=models.SET_NULL, null=True, related_name="files" + ) + professor = models.ForeignKey( + Professor, on_delete=models.SET_NULL, null=True, related_name="files" + ) + semester = models.ForeignKey( + Semester, on_delete=models.SET_NULL, null=True, related_name="files" + ) + course = models.ForeignKey( + Course, on_delete=models.SET_NULL, null=True, related_name="files" + ) + + def __str__(self): + return self.filename diff --git a/backend/api/serializers.py b/backend/api/serializers.py index fca01c9..8b582fa 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import PeerUser +from .models import PeerUser, Topic, Semester, Professor, Course, File class LoginSerializer(serializers.Serializer): @@ -11,3 +11,39 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = PeerUser fields = ["id", "username", "email", "ip_address"] + + +class TopicSerializer(serializers.ModelSerializer): + class Meta: + model = Topic + fields = "__all__" + + +class ProfessorSerializer(serializers.ModelSerializer): + class Meta: + model = Professor + fields = "__all__" + + +class SemesterSerializer(serializers.ModelSerializer): + class Meta: + model = Semester + fields = "__all__" + + +class CourseSerializer(serializers.ModelSerializer): + class Meta: + model = Course + fields = "__all__" + + +class FileSerializer(serializers.ModelSerializer): + original_author = UserSerializer() + peer_users = UserSerializer(many=True) + topic = TopicSerializer() + professor = ProfessorSerializer() + semester = SemesterSerializer() + + class Meta: + model = File + fields = "__all__" diff --git a/backend/api/urls.py b/backend/api/urls.py index 5d35e83..ff906cb 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -16,9 +16,43 @@ Including another URLconf """ from django.urls import path -from api.user_login import LoginView, SignupView +from api.user_login import LoginView, SignupView, PollOnlineView +from api import views urlpatterns = [ path("api/login/", LoginView.as_view(), name="login"), path("api/signup/", SignupView.as_view(), name="signup"), + path("api/poll/", PollOnlineView.as_view(), name="poll"), + path("api/topics/", views.TopicListCreateAPIView.as_view(), name="topic-list"), + path( + "api/topics//", views.TopicDetailAPIView.as_view(), name="topic-detail" + ), + path( + "api/professors/", + views.ProfessorListCreateAPIView.as_view(), + name="professor-list", + ), + path( + "api/professors//", + views.ProfessorDetailAPIView.as_view(), + name="professor-detail", + ), + path( + "api/semesters/", + views.SemesterListCreateAPIView.as_view(), + name="semester-list", + ), + path( + "api/semesters//", + views.SemesterDetailAPIView.as_view(), + name="semester-detail", + ), + path("api/courses/", views.CourseListCreateAPIView.as_view(), name="course-list"), + path( + "api/courses//", + views.CourseDetailAPIView.as_view(), + name="course-detail", + ), + path("api/files/", views.FileListCreateAPIView.as_view(), name="file-list"), + path("api/files//", views.FileDetailAPIView.as_view(), name="file-detail"), ] diff --git a/backend/api/user_login.py b/backend/api/user_login.py index 87b67f4..febb376 100644 --- a/backend/api/user_login.py +++ b/backend/api/user_login.py @@ -4,6 +4,8 @@ from rest_framework import status from rest_framework.authtoken.models import Token from rest_framework.response import Response from rest_framework.views import APIView +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated def get_client_ip(request): @@ -59,3 +61,24 @@ class SignupView(APIView): ) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class PollOnlineView(APIView): + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + def post(self, request): + try: + user = request.user + ip_address = get_client_ip(request) + user.ip_address = ip_address + user.save() + return Response( + { + "Message": "IP address updated successfully", + "username": user.username, + }, + status=status.HTTP_200_OK, + ) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) diff --git a/backend/api/views.py b/backend/api/views.py index 67b2013..7510eb0 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -1,6 +1,101 @@ -from rest_framework.views import APIView -from rest_framework.response import Response -from api.serializers import LoginSerializer +from api.models import Course, File, Professor, Semester, Topic +from api.serializers import ( + CourseSerializer, + FileSerializer, + ProfessorSerializer, + SemesterSerializer, + TopicSerializer, +) from django.contrib.auth import authenticate +from rest_framework import generics, status +from rest_framework.authentication import TokenAuthentication from rest_framework.authtoken.models import Token -from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import filters + + +class TopicListCreateAPIView(generics.ListCreateAPIView): + queryset = Topic.objects.all() + serializer_class = TopicSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ["name"] + + +class TopicDetailAPIView(generics.RetrieveUpdateDestroyAPIView): + queryset = Topic.objects.all() + serializer_class = TopicSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + +class ProfessorListCreateAPIView(generics.ListCreateAPIView): + queryset = Professor.objects.all() + serializer_class = ProfessorSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ["name"] + + +class ProfessorDetailAPIView(generics.RetrieveUpdateDestroyAPIView): + queryset = Professor.objects.all() + serializer_class = ProfessorSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + +class SemesterListCreateAPIView(generics.ListCreateAPIView): + queryset = Semester.objects.all() + serializer_class = SemesterSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ["name"] + + +class SemesterDetailAPIView(generics.RetrieveUpdateDestroyAPIView): + queryset = Semester.objects.all() + serializer_class = SemesterSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + def delete(self, request, *args, **kwargs): + instance = self.get_object() + self.perform_destroy(instance) + return Response(status=status.HTTP_204_NO_CONTENT) + + +class CourseListCreateAPIView(generics.ListCreateAPIView): + queryset = Course.objects.all() + serializer_class = CourseSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ["name"] + + +class CourseDetailAPIView(generics.RetrieveUpdateDestroyAPIView): + queryset = Course.objects.all() + serializer_class = CourseSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + +class FileListCreateAPIView(generics.ListCreateAPIView): + queryset = File.objects.all() + serializer_class = FileSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ["name"] + + +class FileDetailAPIView(generics.RetrieveUpdateDestroyAPIView): + queryset = File.objects.all() + serializer_class = FileSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] diff --git a/backend/peer_notes/settings.py b/backend/peer_notes/settings.py index b2c0bd8..474b470 100644 --- a/backend/peer_notes/settings.py +++ b/backend/peer_notes/settings.py @@ -25,7 +25,7 @@ SECRET_KEY = "django-insecure-rh0s*u8$uugtt10cxbifjriaz%@&p1w!)c2=y^undd2*nx5 # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["10.20.4.109", "73.7.29.136", "localhost"] # Application definition From fcfa7b235429d84d9f21b034b148ff15cfcf496e Mon Sep 17 00:00:00 2001 From: Aman Tahiliani Date: Tue, 26 Mar 2024 19:39:04 -0400 Subject: [PATCH 2/5] Added Register functionality --- .../api/migrations/0005_file_created_at.py | 22 +++++++ backend/api/models.py | 1 + backend/api/urls.py | 1 + backend/api/user_login.py | 10 +-- backend/api/utils/__init__.py | 0 backend/api/utils/get_client_ip.py | 14 ++++ backend/api/views.py | 64 +++++++++++++++++++ backend/api/views/filters.py | 0 8 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 backend/api/migrations/0005_file_created_at.py create mode 100644 backend/api/utils/__init__.py create mode 100644 backend/api/utils/get_client_ip.py create mode 100644 backend/api/views/filters.py diff --git a/backend/api/migrations/0005_file_created_at.py b/backend/api/migrations/0005_file_created_at.py new file mode 100644 index 0000000..a9a38e5 --- /dev/null +++ b/backend/api/migrations/0005_file_created_at.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.11 on 2024-03-26 23:14 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0004_course_number_alter_course_name"), + ] + + operations = [ + migrations.AddField( + model_name="file", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 8e5f1dc..ce8f50a 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -56,6 +56,7 @@ class File(models.Model): course = models.ForeignKey( Course, on_delete=models.SET_NULL, null=True, related_name="files" ) + created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.filename diff --git a/backend/api/urls.py b/backend/api/urls.py index ff906cb..328d9bb 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -55,4 +55,5 @@ urlpatterns = [ ), path("api/files/", views.FileListCreateAPIView.as_view(), name="file-list"), path("api/files//", views.FileDetailAPIView.as_view(), name="file-detail"), + path("api/register/", views.RegisterFile.as_view(), name="file-register"), ] diff --git a/backend/api/user_login.py b/backend/api/user_login.py index febb376..1a8becb 100644 --- a/backend/api/user_login.py +++ b/backend/api/user_login.py @@ -6,15 +6,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated - - -def get_client_ip(request): - x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") - if x_forwarded_for: - ip = x_forwarded_for.split(",")[0] - else: - ip = request.META.get("REMOTE_ADDR") - return ip +from api.utils.get_client_ip import get_client_ip class LoginView(APIView): diff --git a/backend/api/utils/__init__.py b/backend/api/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/utils/get_client_ip.py b/backend/api/utils/get_client_ip.py new file mode 100644 index 0000000..821928c --- /dev/null +++ b/backend/api/utils/get_client_ip.py @@ -0,0 +1,14 @@ +def get_client_ip(request): + x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") + if x_forwarded_for: + ip = x_forwarded_for.split(",")[0] + else: + ip = request.META.get("REMOTE_ADDR") + return ip + + +def update_user_ip(request): + user = request.user + ip_address = get_client_ip(request) + user.ip_address = ip_address + user.save() diff --git a/backend/api/views.py b/backend/api/views.py index 7510eb0..6f5acff 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -14,6 +14,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import filters +from rest_framework.exceptions import ValidationError, NotFound +from api.utils.get_client_ip import update_user_ip class TopicListCreateAPIView(generics.ListCreateAPIView): @@ -99,3 +101,65 @@ class FileDetailAPIView(generics.RetrieveUpdateDestroyAPIView): serializer_class = FileSerializer authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated] + + +class RegisterFile(APIView): + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + def post(self, request): + user = request.user + update_user_ip(request) + data = request.data + try: + required_fields = set( + ["filename", "topic", "semester", "professor", "course"] + ) + provided_fields = set(request.data.keys()) + missing_fields = required_fields - provided_fields + + if any(["filename", "topic"]) in missing_fields: + raise ValidationError( + "Missing one or more of the required field(s): filename, topic" + ) + + file = File.objects.create(filename=data["filename"], original_author=user) + file.peer_users.add(user) + + file.course = ( + Course.objects.get(id=data.get("course")) + if data.get("course") + else None + ) + file.professor = ( + Professor.objects.get(id=data.get("professor")) + if data.get("professor") + else None + ) + file.semester = ( + Semester.objects.get(id=data.get("semester")) + if data.get("semester") + else None + ) + + topic_id = data["topic"] + + try: + topic = Topic.objects.get(id=topic_id) + file.topic = topic + except Exception as e: + topic_serializer = TopicSerializer(data=topic_id) + if topic_serializer.is_valid(): + topic = topic_serializer.save() + file.topic = topic + + file.save() + return Response( + {"id": file.id, "filename": file.filename, "topic": file.topic.name}, + status=status.HTTP_201_CREATED, + ) + except Exception as e: + print("Error", str(e)) + return Response( + {"error(s)": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST + ) diff --git a/backend/api/views/filters.py b/backend/api/views/filters.py new file mode 100644 index 0000000..e69de29 From 3a37faecd94cdf896ba10a427c6c4458f18cbe3c Mon Sep 17 00:00:00 2001 From: Aman Tahiliani Date: Wed, 27 Mar 2024 18:25:51 -0400 Subject: [PATCH 3/5] Added README --- backend/README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 backend/README.md diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..254ccdc --- /dev/null +++ b/backend/README.md @@ -0,0 +1,67 @@ +# Django Backend + +This is the backend for the Django project. + +## Setup + +1. Create a virtual environment: + ```bash + python3 -m venv venv + ``` + +2. Activate the virtual environment: + - For macOS/Linux: + ```bash + source venv/bin/activate + ``` + - For Windows: + ```bash + venv\Scripts\activate + ``` + +3. Install the project dependencies: + ```bash + pip install -r requirements.txt + ``` + +## Running the Server + +To start the Django server, run the following command: +```bash + python3 manage.py runserver +``` + +By default, the server will run on `http://localhost:8000/`. + +## Admin Panel +In order to access the admin panel, you need to create a superuser. To do this, run the following command: +```bash + python3 manage.py createsuperuser +``` + +Then, you can access the admin panel by visiting `http://localhost:8000/admin/` and logging in with the superuser credentials. + +## Contributing to the Codebase +1. Create a new branch: + ```bash + git checkout -b + ``` +2. Make your changes. + +3. Use black to format your code: + ```bash + black . + ``` + +4. Make your changes and commit them: + ```bash + git add . + git commit -m "Your commit message" + ``` +5. Push your changes to the remote repository: + ```bash + git push origin + ``` +6. Create a Pull Request on GitHub. + + From b3c4f7f2e8378e87a1d64d06e19625c30bec8170 Mon Sep 17 00:00:00 2001 From: amantahiliani Date: Mon, 8 Apr 2024 15:59:23 -0400 Subject: [PATCH 4/5] Added Filter based querying for files --- backend/api/urls.py | 1 + backend/api/views.py | 155 +++++++++++++++++++++++++++++++------------ 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/backend/api/urls.py b/backend/api/urls.py index 328d9bb..9409a0b 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -55,5 +55,6 @@ urlpatterns = [ ), path("api/files/", views.FileListCreateAPIView.as_view(), name="file-list"), path("api/files//", views.FileDetailAPIView.as_view(), name="file-detail"), + path("api/files/filter/", views.FileFilterView.as_view(), name="file-filter-view"), path("api/register/", views.RegisterFile.as_view(), name="file-register"), ] diff --git a/backend/api/views.py b/backend/api/views.py index 6f5acff..9e91f6e 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -1,4 +1,4 @@ -from api.models import Course, File, Professor, Semester, Topic +from api.models import Course, File, Professor, Semester, Topic, PeerUser from api.serializers import ( CourseSerializer, FileSerializer, @@ -6,7 +6,9 @@ from api.serializers import ( SemesterSerializer, TopicSerializer, ) +from datetime import datetime, timedelta from django.contrib.auth import authenticate +from django.db import transaction from rest_framework import generics, status from rest_framework.authentication import TokenAuthentication from rest_framework.authtoken.models import Token @@ -112,54 +114,125 @@ class RegisterFile(APIView): update_user_ip(request) data = request.data try: - required_fields = set( - ["filename", "topic", "semester", "professor", "course"] - ) - provided_fields = set(request.data.keys()) - missing_fields = required_fields - provided_fields + with transaction.atomic(): + required_fields = set( + ["filename", "topic", "semester", "professor", "course"] + ) + provided_fields = set(request.data.keys()) + missing_fields = required_fields - provided_fields - if any(["filename", "topic"]) in missing_fields: - raise ValidationError( - "Missing one or more of the required field(s): filename, topic" + if any(["filename", "topic"]) in missing_fields: + raise ValidationError( + "Missing one or more of the required field(s): filename, topic" + ) + + file = File.objects.create( + filename=data["filename"], original_author=user + ) + file.peer_users.add(user) + + file.course = ( + Course.objects.get(id=data.get("course")) + if data.get("course") + else None + ) + file.professor = ( + Professor.objects.get(id=data.get("professor")) + if data.get("professor") + else None + ) + file.semester = ( + Semester.objects.get(id=data.get("semester")) + if data.get("semester") + else None ) - file = File.objects.create(filename=data["filename"], original_author=user) - file.peer_users.add(user) + topic_id = data["topic"] - file.course = ( - Course.objects.get(id=data.get("course")) - if data.get("course") - else None - ) - file.professor = ( - Professor.objects.get(id=data.get("professor")) - if data.get("professor") - else None - ) - file.semester = ( - Semester.objects.get(id=data.get("semester")) - if data.get("semester") - else None - ) - - topic_id = data["topic"] - - try: - topic = Topic.objects.get(id=topic_id) - file.topic = topic - except Exception as e: - topic_serializer = TopicSerializer(data=topic_id) - if topic_serializer.is_valid(): - topic = topic_serializer.save() + try: + topic = Topic.objects.get(id=topic_id) file.topic = topic + except Exception as e: + topic_serializer = TopicSerializer(data=topic_id) + if topic_serializer.is_valid(): + topic = topic_serializer.save() + file.topic = topic - file.save() - return Response( - {"id": file.id, "filename": file.filename, "topic": file.topic.name}, - status=status.HTTP_201_CREATED, - ) + response_data = { + "id": file.id, + "filename": file.filename, + "topic": file.topic.name if file.topic else None, + "semester": file.semester.name if file.semester else None, + "course": file.course.name if file.course else None, + "professor": file.professor.name if file.professor else None, + } + file.save() + return Response( + response_data, + status=status.HTTP_201_CREATED, + ) except Exception as e: print("Error", str(e)) return Response( {"error(s)": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST ) + + +class FileFilterView(APIView): + def get(self, request, format=None): + try: + # Extract filters from the query parameters + topic_id = request.query_params.get("topic") + professor_id = request.query_params.get("professor") + course_id = request.query_params.get("course") + semester_id = request.query_params.get("semester") + + # Check if provided filter IDs exist + if topic_id and not Topic.objects.filter(id=topic_id).exists(): + return Response( + {"error": f"Topic with id {topic_id} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if professor_id and not Professor.objects.filter(id=professor_id).exists(): + return Response( + {"error": f"Professor with id {professor_id} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if course_id and not Course.objects.filter(id=course_id).exists(): + return Response( + {"error": f"Course with id {course_id} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if semester_id and not Semester.objects.filter(id=semester_id).exists(): + return Response( + {"error": f"Semester with id {semester_id} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Start with the base queryset + queryset = File.objects.all() + + # Apply filters if they are provided + if topic_id: + queryset = queryset.filter(topic__id=topic_id) + if professor_id: + queryset = queryset.filter(professor__id=professor_id) + if course_id: + queryset = queryset.filter(course__id=course_id) + if semester_id: + queryset = queryset.filter(semester__id=semester_id) + + # Filter files based on active peers in the past hour + active_peer_ids = PeerUser.objects.filter( + last_poll__gte=datetime.now() - timedelta(hours=1) + ).values_list("id", flat=True) + + queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() + + serializer = FileSerializer(queryset, many=True) + return Response(serializer.data) + except Exception as e: + return Response( + {"error": f"Something went wrong: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) From 99c8a44c1ffcff8fcbd2b60fde981ca6bbcfcf8d Mon Sep 17 00:00:00 2001 From: amantahiliani Date: Mon, 8 Apr 2024 16:05:56 -0400 Subject: [PATCH 5/5] Added authentication to file fetching API --- backend/api/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/api/views.py b/backend/api/views.py index 9e91f6e..5ef87b6 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -179,6 +179,8 @@ class RegisterFile(APIView): class FileFilterView(APIView): + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] def get(self, request, format=None): try: # Extract filters from the query parameters