diff --git a/autograder_django_backend/assignments/__init__.py b/autograder_django_backend/assignments/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autograder_django_backend/assignments/admin.py b/autograder_django_backend/assignments/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/autograder_django_backend/assignments/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/autograder_django_backend/assignments/apps.py b/autograder_django_backend/assignments/apps.py deleted file mode 100644 index d69accf..0000000 --- a/autograder_django_backend/assignments/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class AssignmentsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'assignments' diff --git a/autograder_django_backend/assignments/migrations/0001_initial.py b/autograder_django_backend/assignments/migrations/0001_initial.py deleted file mode 100644 index a04b6b7..0000000 --- a/autograder_django_backend/assignments/migrations/0001_initial.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.12 on 2022-06-15 18:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Assignment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('file', models.FileField(upload_to='assignments/')), - ], - ), - ] diff --git a/autograder_django_backend/assignments/migrations/__init__.py b/autograder_django_backend/assignments/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autograder_django_backend/assignments/models.py b/autograder_django_backend/assignments/models.py deleted file mode 100644 index ea99ade..0000000 --- a/autograder_django_backend/assignments/models.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.db import models - -class Assignment(models.Model): - # TODO: Make IDs use UUIDs - - file = models.FileField(upload_to='assignments/') \ No newline at end of file diff --git a/autograder_django_backend/assignments/serializers.py b/autograder_django_backend/assignments/serializers.py deleted file mode 100644 index 918b14a..0000000 --- a/autograder_django_backend/assignments/serializers.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework.serializers import Serializer, FileField - -# Serializers define the API representation. -class UploadSerializer(Serializer): - file_uploaded = FileField() - class Meta: - fields = ['file_uploaded'] \ No newline at end of file diff --git a/autograder_django_backend/assignments/tests.py b/autograder_django_backend/assignments/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/autograder_django_backend/assignments/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/autograder_django_backend/assignments/urls.py b/autograder_django_backend/assignments/urls.py deleted file mode 100644 index 6e05b82..0000000 --- a/autograder_django_backend/assignments/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path, include -from rest_framework import routers -from .views import UploadViewSet - -router = routers.DefaultRouter() -router.register(r'upload', UploadViewSet, basename="upload") - -# Wire up our API using automatic URL routing. -urlpatterns = [ - path('', include(router.urls)), -] \ No newline at end of file diff --git a/autograder_django_backend/assignments/views.py b/autograder_django_backend/assignments/views.py deleted file mode 100644 index 2955fda..0000000 --- a/autograder_django_backend/assignments/views.py +++ /dev/null @@ -1,45 +0,0 @@ -from rest_framework.viewsets import ViewSet -from rest_framework.response import Response -from .serializers import UploadSerializer -from rest_framework.parsers import FormParser, MultiPartParser -import requests - -# ViewSets define the view behavior. - - -class UploadViewSet(ViewSet): - serializer_class = UploadSerializer - - parser_classes = [MultiPartParser, FormParser] - - def list(self, request): - return Response("GET API") - - def create(self, request): - file_uploaded = request.data['file_uploaded'] - content_type = file_uploaded.content_type - response = "POST API and you have uploaded a {} file".format(content_type) - - # Make sure it's a zip file - if content_type != 'application/zip': - response = "Please upload a zip file" - else: - # Add the job to the DB - - - # Start a websocket connection with the scheduler - - # Pass the ID of the job - - - # Send request to scheduler - url = 'http://localhost:4000/add_job' - files = {'file_uploaded': file_uploaded} - r = requests.post(url, files=files) - response = r.text - - print(response) - - - - return Response(response) diff --git a/autograder_django_backend/autograder/serializers.py b/autograder_django_backend/autograder/serializers.py new file mode 100644 index 0000000..b5927e4 --- /dev/null +++ b/autograder_django_backend/autograder/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from .models import Assignment + + +class AssignmentSerializer(serializers.ModelSerializer): + class Meta: + model = Assignment + fields = "__all__" diff --git a/autograder_django_backend/autograder/tests/factory.py b/autograder_django_backend/autograder/tests/factory.py index 83e95db..0da6ae6 100644 --- a/autograder_django_backend/autograder/tests/factory.py +++ b/autograder_django_backend/autograder/tests/factory.py @@ -1,5 +1,5 @@ from faker import Faker -from autograder.models import Student +from autograder.models import Assignment, Student fake = Faker() @@ -19,3 +19,21 @@ def student_factory(**kwargs): student = Student.objects.create(**kwargs) return student + + +def assignment_factory(**kwargs): + if "name" not in kwargs: + kwargs["name"] = fake.name() + if "description" not in kwargs: + kwargs["description"] = fake.text() + if "due_date" not in kwargs: + kwargs["due_date"] = fake.date_time_between(start_date="-1y", end_date="now") + if "assignment_spec" not in kwargs: + kwargs["assignment_spec"] = fake.text() + + # Check that there are only 4 arguments in kwargs + assert len(kwargs) == 4 + + student = Assignment.objects.create(**kwargs) + + return student diff --git a/autograder_django_backend/autograder/tests/test_assignment.py b/autograder_django_backend/autograder/tests/test_assignment.py index e69de29..5389850 100644 --- a/autograder_django_backend/autograder/tests/test_assignment.py +++ b/autograder_django_backend/autograder/tests/test_assignment.py @@ -0,0 +1,38 @@ +from django.test import TestCase +import pytest +from autograder.models import Course, Assignment, Professor, Student +from autograder_django_backend.utils.tests import get_client + +# Test creating a course through the API +@pytest.mark.django_db(transaction=True) +def test_create_assignment_api(): + client, user = get_client() + + professor = Professor.objects.create(name="test_professor", email="test@test.com") + + course = Course.objects.create( + course_id="test_course", + name="test_course", + description="test_course", + section="test_course", + professor=professor, + ) + + response = client.post( + "/api/assignments/", + { + "name": "test_assignment", + "description": "test_description", + "due_date": "2020-01-01T00:00:00Z", + "assignment_spec": "test_assignment_spec", + "course": course.id, + }, + format="json", + ) + + assert response.status_code == 201 + + assignment = Assignment.objects.get(name="test_assignment") + + assert assignment.name == "test_assignment" + assert assignment.description == "test_description" diff --git a/autograder_django_backend/autograder/views.py b/autograder_django_backend/autograder/views.py index 91ea44a..8751e05 100644 --- a/autograder_django_backend/autograder/views.py +++ b/autograder_django_backend/autograder/views.py @@ -1,3 +1,9 @@ -from django.shortcuts import render +from rest_framework import viewsets -# Create your views here. +from .models import Assignment +from .serializers import AssignmentSerializer + + +class AssignmentViewSet(viewsets.ModelViewSet): + queryset = Assignment.objects.all() + serializer_class = AssignmentSerializer diff --git a/autograder_django_backend/autograder_django_backend/contrib/sites/migrations/0003_set_site_domain_and_name.py b/autograder_django_backend/autograder_django_backend/contrib/sites/migrations/0003_set_site_domain_and_name.py index 0f5ce9c..fd21e19 100644 --- a/autograder_django_backend/autograder_django_backend/contrib/sites/migrations/0003_set_site_domain_and_name.py +++ b/autograder_django_backend/autograder_django_backend/contrib/sites/migrations/0003_set_site_domain_and_name.py @@ -23,7 +23,7 @@ def _update_or_create_site_with_sequence(site_model, connection, domain, name): # site is created. # To avoid this, we need to manually update DB sequence and make sure it's # greater than the maximum value. - max_id = site_model.objects.order_by('-id').first().id + max_id = site_model.objects.order_by("-id").first().id with connection.cursor() as cursor: cursor.execute("SELECT last_value from django_site_id_seq") (current_id,) = cursor.fetchone() diff --git a/autograder_django_backend/autograder_django_backend/users/admin.py b/autograder_django_backend/autograder_django_backend/users/admin.py index 71eb3c2..89cc00d 100644 --- a/autograder_django_backend/autograder_django_backend/users/admin.py +++ b/autograder_django_backend/autograder_django_backend/users/admin.py @@ -3,7 +3,10 @@ from django.contrib.auth import get_user_model from django.utils.translation import gettext_lazy as _ -from autograder_django_backend.users.forms import UserAdminChangeForm, UserAdminCreationForm +from autograder_django_backend.users.forms import ( + UserAdminChangeForm, + UserAdminCreationForm, +) User = get_user_model() diff --git a/autograder_django_backend/autograder_django_backend/utils/tests.py b/autograder_django_backend/autograder_django_backend/utils/tests.py new file mode 100644 index 0000000..c804bd9 --- /dev/null +++ b/autograder_django_backend/autograder_django_backend/utils/tests.py @@ -0,0 +1,16 @@ +from rest_framework.test import APIClient +from autograder_django_backend.users.models import User + + +def get_client(): + """ + Returns a Django test client. + """ + + client = APIClient() + + test_user = User.objects.create_user(username="test_user", password="test_password") + + client.login(username="test_user", password="test_password") + + return (client, test_user) diff --git a/autograder_django_backend/config/api_router.py b/autograder_django_backend/config/api_router.py index e2721e4..0ddd1ea 100644 --- a/autograder_django_backend/config/api_router.py +++ b/autograder_django_backend/config/api_router.py @@ -1,5 +1,6 @@ from django.conf import settings from rest_framework.routers import DefaultRouter, SimpleRouter +from autograder.views import AssignmentViewSet from autograder_django_backend.users.api.views import UserViewSet @@ -9,6 +10,7 @@ router = SimpleRouter() router.register("users", UserViewSet) +router.register("assignments", AssignmentViewSet) app_name = "api" diff --git a/autograder_django_backend/config/settings/base.py b/autograder_django_backend/config/settings/base.py index 64c58fc..0f5a47b 100644 --- a/autograder_django_backend/config/settings/base.py +++ b/autograder_django_backend/config/settings/base.py @@ -80,9 +80,7 @@ LOCAL_APPS = [ "autograder_django_backend.users", - "assignments", "autograder", - # Your stuff: custom apps go here ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS @@ -297,7 +295,9 @@ # https://django-allauth.readthedocs.io/en/latest/configuration.html SOCIALACCOUNT_ADAPTER = "autograder_django_backend.users.adapters.SocialAccountAdapter" # https://django-allauth.readthedocs.io/en/latest/forms.html -SOCIALACCOUNT_FORMS = {"signup": "autograder_django_backend.users.forms.UserSocialSignupForm"} +SOCIALACCOUNT_FORMS = { + "signup": "autograder_django_backend.users.forms.UserSocialSignupForm" +} # django-rest-framework # ------------------------------------------------------------------------------- @@ -328,7 +328,10 @@ "SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"], "SERVERS": [ {"url": "http://127.0.0.1:8000", "description": "Local Development server"}, - {"url": "https://autograder.ccss.carleton.ca", "description": "Production server"}, + { + "url": "https://autograder.ccss.carleton.ca", + "description": "Production server", + }, ], } # Your stuff... diff --git a/autograder_django_backend/config/settings/local.py b/autograder_django_backend/config/settings/local.py index aa30518..691a08d 100644 --- a/autograder_django_backend/config/settings/local.py +++ b/autograder_django_backend/config/settings/local.py @@ -66,3 +66,28 @@ CELERY_TASK_EAGER_PROPAGATES = True # Your stuff... # ------------------------------------------------------------------------------ + + +# django-rest-framework +# ------------------------------------------------------------------------------- +# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/ +REST_FRAMEWORK = { + # "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +# By Default swagger ui is available only to admin user(s). You can change permission classes to change that +# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings +SPECTACULAR_SETTINGS = { + "TITLE": "Autograder Django Backend API", + "DESCRIPTION": "Documentation of API endpoints of Autograder Django Backend", + "VERSION": "1.0.0", + "SERVE_PERMISSIONS": [], + "SERVERS": [ + {"url": "http://127.0.0.1:8000", "description": "Local Development server"}, + { + "url": "https://autograder.ccss.carleton.ca", + "description": "Production server", + }, + ], +} diff --git a/autograder_django_backend/config/settings/production.py b/autograder_django_backend/config/settings/production.py index b91de8d..adffa90 100644 --- a/autograder_django_backend/config/settings/production.py +++ b/autograder_django_backend/config/settings/production.py @@ -14,7 +14,9 @@ # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key SECRET_KEY = env("DJANGO_SECRET_KEY") # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["autograder.ccss.carleton.ca"]) +ALLOWED_HOSTS = env.list( + "DJANGO_ALLOWED_HOSTS", default=["autograder.ccss.carleton.ca"] +) # DATABASES # ------------------------------------------------------------------------------ diff --git a/autograder_django_backend/config/settings/test.py b/autograder_django_backend/config/settings/test.py index 61c9ef9..3e6fe6b 100644 --- a/autograder_django_backend/config/settings/test.py +++ b/autograder_django_backend/config/settings/test.py @@ -32,4 +32,4 @@ "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", } -} \ No newline at end of file +} diff --git a/autograder_django_backend/config/urls.py b/autograder_django_backend/config/urls.py index 8a79a54..f32efef 100644 --- a/autograder_django_backend/config/urls.py +++ b/autograder_django_backend/config/urls.py @@ -7,9 +7,17 @@ from django.views.generic import TemplateView from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView from rest_framework.authtoken.views import obtain_auth_token +from rest_framework import permissions, routers + +from autograder.views import AssignmentViewSet + +router = routers.DefaultRouter(trailing_slash=True) +router.register("assignments", AssignmentViewSet) urlpatterns = [ + # DRF router + path("api/", include(router.urls)), path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), path( "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" @@ -19,8 +27,6 @@ # User management path("users/", include("autograder_django_backend.users.urls", namespace="users")), path("accounts/", include("allauth.urls")), - # Your stuff: custom urls includes go here - path("api/assignments/", include("assignments.urls")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: # Static file serving when using Gunicorn + Uvicorn for local web socket development