Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialise Backend with Certain Dedicated Features #47

Merged
merged 94 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
bf77a16
basic body card
MouseAndKeyboard Jul 5, 2022
08e6f16
Created general card comopnent
MouseAndKeyboard Jul 5, 2022
95a92c9
reviewer page general structure
MouseAndKeyboard Jul 8, 2022
a3d5fe9
Added archive button toggle
MouseAndKeyboard Jul 8, 2022
dd10698
course evaluation tests
MouseAndKeyboard Jul 18, 2022
095244c
Revert "Added archive button toggle"
MouseAndKeyboard Jul 19, 2022
ec5cca6
Revert "Revert "Added archive button toggle""
MouseAndKeyboard Jul 19, 2022
c20f1a3
Revert "course evaluation tests"
MouseAndKeyboard Jul 19, 2022
09fd850
fixed evaluation tests
MouseAndKeyboard Jul 19, 2022
e23e9ae
created reviews app
MouseAndKeyboard Jul 19, 2022
a7d544c
moved reviews model to new app
MouseAndKeyboard Jul 19, 2022
59fc046
created documents app
MouseAndKeyboard Jul 19, 2022
e951949
added documents app
MouseAndKeyboard Jul 19, 2022
7fd77f2
added documents migration
MouseAndKeyboard Jul 19, 2022
18a0aab
added model for revieweocspecific
MouseAndKeyboard Jul 19, 2022
a585a1f
added document reviews and eoc specific reviews
MouseAndKeyboard Jul 19, 2022
f9303db
document and eocspecific review migrations
MouseAndKeyboard Jul 19, 2022
c68f498
basic body card
MouseAndKeyboard Jul 5, 2022
f5b33a9
Created general card comopnent
MouseAndKeyboard Jul 5, 2022
cf71f77
reviewer page general structure
MouseAndKeyboard Jul 8, 2022
926de72
Added archive button toggle
MouseAndKeyboard Jul 8, 2022
012462c
course evaluation tests
MouseAndKeyboard Jul 18, 2022
0e5cef2
Revert "Added archive button toggle"
MouseAndKeyboard Jul 19, 2022
1a52e90
Revert "Revert "Added archive button toggle""
MouseAndKeyboard Jul 19, 2022
aa25c41
Revert "course evaluation tests"
MouseAndKeyboard Jul 19, 2022
f79d848
fixed evaluation tests
MouseAndKeyboard Jul 19, 2022
0ef98f8
created reviews app
MouseAndKeyboard Jul 19, 2022
8019aae
moved reviews model to new app
MouseAndKeyboard Jul 19, 2022
60b8e51
created documents app
MouseAndKeyboard Jul 19, 2022
54f03b2
added documents app
MouseAndKeyboard Jul 19, 2022
e2323e5
added documents migration
MouseAndKeyboard Jul 19, 2022
6c8f4a8
added model for revieweocspecific
MouseAndKeyboard Jul 19, 2022
eeef153
added document reviews and eoc specific reviews
MouseAndKeyboard Jul 19, 2022
8a6c5cc
document and eocspecific review migrations
MouseAndKeyboard Jul 19, 2022
c14949c
Merge branch '44-review-listing-frontend' of github.com:uwasystemheal…
MouseAndKeyboard Jul 19, 2022
3aac48a
ongoing reviews list component
MouseAndKeyboard Jul 19, 2022
48b90d4
removed unecessary import
MouseAndKeyboard Jul 21, 2022
ea69025
added course evaluation justifications
MouseAndKeyboard Jul 21, 2022
6d4ea52
migrations for courseevaluationsjustifications
MouseAndKeyboard Jul 21, 2022
893b2c1
Merge branch 'develop' into 44-review-listing-frontend
frinzekt Aug 16, 2022
02ca60b
reorganise django installed apps
frinzekt Aug 16, 2022
eed2dc4
linting
frinzekt Aug 16, 2022
6ed1940
running the linter and sorter
frinzekt Aug 16, 2022
8c281ca
resetting migrations for this changes
frinzekt Aug 16, 2022
dfbd5c1
corrections for the field and choices
frinzekt Aug 16, 2022
f37c799
recreate migrations
frinzekt Aug 16, 2022
7709812
change string repr of the justification
frinzekt Aug 16, 2022
258e5d8
relations and information about the documents
frinzekt Aug 16, 2022
1d7b5bb
revise django admin for documents
frinzekt Aug 16, 2022
c46e52e
permissions for editting documents
frinzekt Aug 16, 2022
85f0a8b
listing for the documents
frinzekt Aug 16, 2022
88b953a
remove need of the endpoint
frinzekt Aug 16, 2022
c01e62a
more comment of the document
frinzekt Aug 16, 2022
899ad57
comment for the serializer
frinzekt Aug 16, 2022
7374a85
renaming of the course_evaluation
frinzekt Aug 16, 2022
859cf0c
squash new migrations
frinzekt Aug 16, 2022
c5d3f21
Merge branch 'develop' into 44-review-listing-frontend
frinzekt Aug 18, 2022
bd6a773
comment for the "write" of part of the eoc_set
frinzekt Aug 18, 2022
68b6651
fix test with object level permission
frinzekt Aug 18, 2022
362ed0c
move the document viewset as part of course evaluation
frinzekt Aug 18, 2022
bf73ac2
fix tests permissions
frinzekt Aug 18, 2022
e914598
remove documents in the path
frinzekt Aug 18, 2022
28ba2f9
permissions for documents
frinzekt Aug 18, 2022
5ec1750
make date_submitted not required
frinzekt Aug 18, 2022
49dca75
correct the reviewer field (typo: coordinator)
frinzekt Aug 18, 2022
23cb334
fix where date time field is required
frinzekt Aug 18, 2022
26487f6
rework on the permissions for coordinator and reviewer
frinzekt Aug 18, 2022
6cefcd6
read and write only serializers
frinzekt Aug 18, 2022
ab7a4b2
rename `get_general_and_specific_eoc` -> `general_and_specific_eoc`
frinzekt Aug 18, 2022
981cb3b
force the course_evaluation value to the url
frinzekt Aug 18, 2022
0e8aea0
change the serializer being displayed to show the general_and_specifi…
frinzekt Aug 18, 2022
3fe2b20
make `course_evaluation` field only a write field
frinzekt Aug 18, 2022
f2daa4e
make assignment of eocs to a document optional
frinzekt Aug 18, 2022
6ffde58
initialise testing for course evaluation document
frinzekt Aug 18, 2022
a83a7ce
tests fixtures for making reviews
frinzekt Aug 18, 2022
b7e8ce4
initialise test permissions
frinzekt Aug 18, 2022
70580c9
added constraint to make a course evaluation and a reviewer unique
frinzekt Aug 18, 2022
4f78e9e
initialise permission for review owner or coordinator perms
frinzekt Aug 18, 2022
cc6f75f
permission for a course review
frinzekt Aug 18, 2022
8d3fe8a
ability to admit and revoke permissions
frinzekt Aug 18, 2022
a6fea5e
fix missing __init__.py
frinzekt Aug 18, 2022
44a5f11
remove unnecessary tagging in pytest
frinzekt Aug 18, 2022
252e202
initiialise permission of a reviewer
frinzekt Aug 18, 2022
0b6cfe2
linting for migration
frinzekt Aug 18, 2022
39c3fa2
initialise test crud as a coordinator
frinzekt Aug 18, 2022
768151c
change to a coordinator exclusive endpoint permission
frinzekt Aug 18, 2022
b5e7ded
flake8 fix
frinzekt Aug 18, 2022
b6dc3a5
move `documents` folder to `course_evaluation`
frinzekt Aug 18, 2022
7a5bd6a
renew migrations with the document models
frinzekt Aug 18, 2022
4d6e3df
fix tests failing due to non-permission of reviewer
frinzekt Aug 18, 2022
7d7865d
flake8 fix
frinzekt Aug 18, 2022
86a2d4e
Merge branch 'develop' into 44-review-listing-frontend
frinzekt Aug 19, 2022
c497bab
Merge branch 'develop' into 44-review-listing-frontend
frinzekt Aug 19, 2022
67b7b52
remove unused comment
frinzekt Aug 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"django_filters",
"commands",
"course_evaluations",
"reviews",
# Authentication
"rest_framework",
"rest_framework.authtoken",
Expand Down
7 changes: 7 additions & 0 deletions backend/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
),
),
path("authentication/", include("authentication.urls", namespace="authentication")),
path(
"reviews/",
include(
("reviews.urls", "reviews"),
namespace="reviews",
),
),
],
"api",
)
Expand Down
84 changes: 83 additions & 1 deletion backend/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from django.core.management import call_command
from rest_framework.test import APIClient

from course_evaluations.models import CourseEvaluation, EOCSet
from course_evaluations.models import CourseEvaluation, Document, EOCSet
from reviews.models import Review


@pytest.fixture
Expand Down Expand Up @@ -142,3 +143,84 @@ def _make_course_evaluation(
# Teardown
for course_evaluation in created_course_evaluation:
course_evaluation.delete()


@pytest.fixture
@pytest.mark.django_db
def make_course_evaluation_document(setup_indeaa, make_course_evaluation) -> Document:
"""Make CourseEvaluation on demand inside tests"""
created_course_evaluation_document = []

# Create a CourseEvaluation record
def _make_course_evaluation_document(
course_evaluation=None,
name="Test CourseEvaluation Document",
description="Test CourseEvaluation Document",
url="https://systemhealthlab.com/",
is_introduction=False,
eoc_generals=[],
eoc_specifics=[],
):
if course_evaluation is None:
course_evaluation = make_course_evaluation()
# Create the record
document = Document.objects.create(
name=name,
description=description,
url=url,
is_introduction=is_introduction,
course_evaluation=course_evaluation,
)

for eoc_general in eoc_generals:
document.eoc_generals.add(eoc_general)

for eoc_specific in eoc_specifics:
document.eoc_specifics.add(eoc_specific)

created_course_evaluation_document.append(document)

return document

# Recommended reading: https://docs.pytest.org/en/stable/fixture.html#yield-fixtures-recommended
yield _make_course_evaluation_document

# Teardown
for document in created_course_evaluation_document:
document.delete()


@pytest.fixture
@pytest.mark.django_db
def make_course_review(setup_indeaa, make_course_evaluation, create_user) -> CourseEvaluation:
"""Make CourseEvaluation on demand inside tests"""
created_course_review = []

# Create a CourseEvaluation record
def _make_course_review(
course_evaluation=None,
reviewer=None,
final_comment="Test CourseEvaluation Review",
date_submitted=None,
):
if course_evaluation is None:
course_evaluation = make_course_evaluation()
if reviewer is None:
reviewer = create_user()

# Create the record
course_review = Review.objects.create(
course_evaluation=course_evaluation,
reviewer=reviewer,
final_comment=final_comment,
date_submitted=date_submitted,
)

return course_review

# Recommended reading: https://docs.pytest.org/en/stable/fixture.html#yield-fixtures-recommended
yield _make_course_review

# Teardown
for document in created_course_review:
document.delete()
26 changes: 24 additions & 2 deletions backend/course_evaluations/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from django.contrib import admin

from course_evaluations.models import CourseEvaluation, EOCGeneral, EOCSet, EOCSpecific
from course_evaluations.models import (
CourseEvaluation,
CourseEvaluationJustification,
EOCGeneral,
EOCSet,
EOCSpecific,
)


@admin.register(EOCSet)
Expand Down Expand Up @@ -33,12 +39,28 @@ class EOCGeneralAdmin(admin.ModelAdmin):

@admin.register(EOCSpecific)
class EOCSpecificAdmin(admin.ModelAdmin):
list_display = ("id", "number", "eoc_general", "get_general_and_specific_eoc", "description")
list_display = (
"id",
"number",
"eoc_general",
"general_and_specific_eoc",
"description",
)
list_filter = ("eoc_general",)
search_fields = ("id", "number", "description")
ordering = ("number",)


@admin.register(CourseEvaluationJustification)
class CourseEvaluationJustificationAdmin(admin.ModelAdmin):
list_display = ("id", "course_evaluation", "justification")
list_filter = ("course_evaluation",)
search_fields = ("id", "justification")
ordering = ("id",)

filter_horizontal = ("eoc_specifics",)


@admin.register(CourseEvaluation)
class CourseEvaluationAdmin(admin.ModelAdmin):
list_display = ("id", "unit_code", "description", "eoc_set")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.2.13 on 2022-08-16 05:20

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("course_evaluations", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="CourseEvaluationJustification",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("justification", models.TextField(blank=True)),
(
"development_level",
models.IntegerField(choices=[(1, "Foundational"), (2, "Broad And Coherent"), (3, "Advanced"), (4, "Specialist")]),
),
("course_evaluation", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="course_evaluations.courseevaluation")),
("eoc_specifics", models.ManyToManyField(related_name="justification", to="course_evaluations.EOCSpecific")),
],
),
]
36 changes: 36 additions & 0 deletions backend/course_evaluations/migrations/0003_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 3.2.13 on 2022-08-18 06:53

import uuid

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("course_evaluations", "0002_courseevaluationjustification"),
]

operations = [
migrations.CreateModel(
name="Document",
fields=[
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("name", models.CharField(max_length=50)),
("description", models.TextField(blank=True)),
("url", models.URLField()),
("is_introduction", models.BooleanField()),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"course_evaluation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="documents", to="course_evaluations.courseevaluation"
),
),
("eoc_generals", models.ManyToManyField(blank=True, to="course_evaluations.EOCGeneral")),
("eoc_specifics", models.ManyToManyField(blank=True, to="course_evaluations.EOCSpecific")),
],
),
]
62 changes: 58 additions & 4 deletions backend/course_evaluations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ class Meta:
eoc_general = models.ForeignKey(EOCGeneral, on_delete=models.CASCADE)

def __str__(self):
return f"{self.eoc_general.eoc_set.name} - {self.get_general_and_specific_eoc()}"
return f"{self.eoc_general.eoc_set.name} - {self.general_and_specific_eoc()}"

def get_general_and_specific_eoc(self):
def general_and_specific_eoc(self):
return f"{self.eoc_general.number}.{self.number}"


Expand All @@ -72,11 +72,65 @@ class CourseEvaluation(models.Model):
# Many-to-many Relationship with the Django User model
# related_name: Allows to reference `CourseEvaluation` from User model
coordinators = models.ManyToManyField("auth.User", related_name="course_evaluation_coordinator")

eoc_set = models.ForeignKey(EOCSet, on_delete=models.CASCADE)

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.eoc_set.name} - {self.unit_code} ({self.created_at})"


class DevelopmentLevels(models.IntegerChoices):
"""
Foundational - Developing a foundation for university level study
Broad and Coherent - Sufficient capability to enter the workforce as a non-engineer
Advanced - Sufficient capability for professional practice as a starting engineer
Specialist - Selected areas of strength beyond the requirement for entering professional practice
"""

FOUNDATIONAL = 1
BROAD_AND_COHERENT = 2
ADVANCED = 3
SPECIALIST = 4


class CourseEvaluationJustification(models.Model):
"""
A course coodinator must provide some written justification for how their course
is acheiving a variety of EOCs. It may be the case that a single written justification
can be used for multiple different EOC specifics.
"""

course_evaluation = models.ForeignKey(CourseEvaluation, on_delete=models.CASCADE)
eoc_specifics = models.ManyToManyField(EOCSpecific, related_name="justification")
justification = models.TextField(null=False, blank=True)
development_level = models.IntegerField(choices=DevelopmentLevels.choices)

def __str__(self):
eoc_specifics = ", ".join([eoc_specific.general_and_specific_eoc() for eoc_specific in self.eoc_specifics.all()])
return f"Course Evaluation Justification ({self.course_evaluation.unit_code}) - {eoc_specifics}"


class Document(models.Model):
"""
A document is a url to a resource that a reviewer will look at
to judge the quality of a course
"""

id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

# Main relations
course_evaluation = models.ForeignKey(CourseEvaluation, on_delete=models.CASCADE, related_name="documents")

# Information about the document
name = models.CharField(max_length=50, null=False, blank=False)
description = models.TextField(null=False, blank=True)
url = models.URLField()

# Tags Equivalence
is_introduction = models.BooleanField()
eoc_generals = models.ManyToManyField(EOCGeneral, blank=True)
eoc_specifics = models.ManyToManyField(EOCSpecific, blank=True)

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
20 changes: 18 additions & 2 deletions backend/course_evaluations/permissions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
from rest_framework import permissions


class IsCoordinatorAllowAll(permissions.BasePermission):
class CourseEvaluationIsCoordinatorAllowAll(permissions.BasePermission):
"""
Custom permission to only allow coordinators the API
"""

def has_object_permission(self, request, view, obj):
"""
A coordinator should be allowed to perform any operation
"""

Note: This only applies for methods that calls `.get_object()`
see https://stackoverflow.com/questions/69959797/has-object-permission-not-working-for-detail-action-decorator
"""
return request.user in obj.coordinators.all()


class CourseEvaluationIsCoordinatorAllowAllViaObjectReference(permissions.BasePermission):
"""
Custom permission to only allow coordinators the API based on the query parameters in the URL
"""

def has_permission(self, request, view):
course_evaluation_id = view.kwargs["course_evaluation_id"]

# Check whether the user has a course_evaluation that is desired
course_evaluations = request.user.course_evaluation_coordinator.all()
if course_evaluations.filter(id=course_evaluation_id).exists():
return True
Loading