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