Skip to content
This repository has been archived by the owner on Dec 26, 2021. It is now read-only.
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pkuphysu/MeetPlanGraphene
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 44ca746f39fbd5cbdf3ac34c6860eb9ea9eee2a4
Choose a base ref
..
head repository: pkuphysu/MeetPlanGraphene
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a4fea62c9ca022aad3b57f8ceed82287661fdc9e
Choose a head ref
19 changes: 19 additions & 0 deletions MeetPlan/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import graphene
from django.conf import settings
from graphene_django.debug import DjangoDebug

from apps.meet_plan.schema import Query as MeetPlanQuery, Mutation as MeetPlanMutation
from apps.pku_auth.schema import Query as AuthQuery, Mutation as AuthMutation
from apps.user.schema import Query as UserQuery, Mutation as UserMutation


class Query(MeetPlanQuery, UserQuery, AuthQuery, graphene.ObjectType):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name="_debug")


class Mutation(MeetPlanMutation, UserMutation, AuthMutation, graphene.ObjectType):
pass


schema = graphene.Schema(query=Query, mutation=Mutation)
35 changes: 34 additions & 1 deletion MeetPlan/settings.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from datetime import timedelta
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -34,7 +35,9 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"graphene_django",
"django_filters",
"graphql_jwt.refresh_token",
"guardian",
"apps.user",
"apps.pku_auth",
@@ -151,21 +154,51 @@
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = "static/"
STATIC_URL = "/static/"

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# the schema location for Graphene
# https://docs.graphene-python.org/projects/django/en/latest/installation/
GRAPHENE = {
"SCHEMA": "MeetPlan.schema.schema",
"ATOMIC_MUTATIONS": True,
"MIDDLEWARE": [
"graphql_jwt.middleware.JSONWebTokenMiddleware",
],
}
if DEBUG:
GRAPHENE["MIDDLEWARE"].append("graphene_django.debug.DjangoDebugMiddleware")

AUTH_USER_MODEL = "user.User"

AUTHENTICATION_BACKENDS = [
"apps.pku_auth.backends.OpenIDClientBackend",
"graphql_jwt.backends.JSONWebTokenBackend",
"guardian.backends.ObjectPermissionBackend",
"django.contrib.auth.backends.ModelBackend",
]

GRAPHQL_JWT = {
"JWT_VERIFY_EXPIRATION": True,
"JWT_LONG_RUNNING_REFRESH_TOKEN": True,
"JWT_REUSE_REFRESH_TOKENS": True,
"JWT_EXPIRATION_DELTA": timedelta(hours=1),
"JWT_REFRESH_EXPIRATION_DELTA": timedelta(days=7),
"JWT_ALLOW_ANY_CLASSES": [
"apps.pku_auth.schema.ObtainJSONWebToken",
"apps.pku_auth.schema.Verify",
"apps.pku_auth.schema.Refresh",
"apps.pku_auth.schema.Revoke",
"apps.pku_auth.schema.RevokeAll",
],
}

GRAPHENE_DJANGO_PLUS = {"MUTATIONS_INCLUDE_REVERSE_RELATIONS": False}

# Django Guardian
# https://django-guardian.readthedocs.io/en/stable/configuration.html#anonymous-user-name
ANONYMOUS_USER_NAME = "0000000000"
4 changes: 4 additions & 0 deletions MeetPlan/urls.py
Original file line number Diff line number Diff line change
@@ -17,8 +17,12 @@
from django.contrib import admin
from django.urls import path, include

from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView

urlpatterns = [
path("admin/", admin.site.urls),
path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

if settings.DEBUG:
11 changes: 6 additions & 5 deletions apps/meet_plan/models.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from graphene_django_plus.models import GuardedModel, GuardedModelManager


def get_start_date():
@@ -19,16 +20,16 @@ def get_start_date():
return start_date


class MeetPlanManager(models.Manager):
class MeetPlanManager(GuardedModelManager):
def get_queryset(self, start_date=None):
if start_date is None:
return super(MeetPlanManager, self).get_queryset()
else:
return super(MeetPlanManager, self).get_queryset().filter(start_time__gt=start_date)


class MeetPlan(models.Model):
teacher = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="meet_plan_set")
class MeetPlan(GuardedModel):
teacher = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="meet_plan")
place = models.CharField(_("place"), max_length=100)
start_time = models.DateTimeField(_("start time"))
duration = models.PositiveSmallIntegerField(
@@ -38,7 +39,7 @@ class MeetPlan(models.Model):
)
t_message = models.TextField(_("teacher message"), blank=True)
student = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="meet_plan_order_set", null=True, blank=True
settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="meet_plan_order", null=True, blank=True
)
s_message = models.TextField(_("student message"), blank=True)
complete = models.BooleanField(_("status"), default=False)
@@ -60,7 +61,7 @@ def save(self, **kwargs):
super().save(kwargs)


class TermDate(models.Model):
class TermDate(GuardedModel):
start_date = models.DateTimeField(_("term start date"))

class Meta:
17 changes: 17 additions & 0 deletions apps/meet_plan/schema/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .query import (
TermDateType,
MeetPlanType,
Query,
)
from .mutation import TermDateCreate, MeetPlanCreate, MeetPlanUpdate, MeetPlanDelete, Mutation

__all__ = [
"TermDateType",
"MeetPlanType",
"Query",
"TermDateCreate",
"MeetPlanCreate",
"MeetPlanUpdate",
"MeetPlanDelete",
"Mutation",
]
150 changes: 150 additions & 0 deletions apps/meet_plan/schema/mutation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import graphene
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from graphene_django_plus.exceptions import PermissionDenied
from graphene_django_plus.mutations import ModelCreateMutation, ModelUpdateMutation, ModelDeleteMutation
from guardian.shortcuts import assign_perm

from apps.meet_plan.models import TermDate, MeetPlan


class TermDateCreate(ModelCreateMutation):
class Meta:
model = TermDate
permissions = ["meet_plan.add_termdate"]
only_fields = ["start_date"]


class MeetPlanCreate(ModelCreateMutation):
class Meta:
model = MeetPlan
only_fields = [
"teacher",
"place",
"start_time",
"duration",
"t_message",
"student",
"s_message",
"complete",
]

@classmethod
def before_save(cls, info, instance, cleaned_input=None):
user = info.context.user
if user.is_admin:
pass
elif user.is_teacher:
if user.id != instance.teacher_id:
raise ValidationError({"teacher": _("You can only create your own meet plan.")})
else:
if instance.student_id is None or user.id != instance.student_id:
raise ValidationError({"student": _("You can only create your own meet plan.")})
if instance.start_time > timezone.now():
raise ValidationError({"start_time": _("You can only create the previous plan.")})
if instance.complete:
raise ValidationError(
{"complete": _("You can only create incomplete plan and ask the teacher to confirm it.")}
)

@classmethod
def after_save(cls, info, instance, cleaned_input=None):
assign_perm("meet_plan.change_meetplan", instance.teacher, instance)
assign_perm("meet_plan.delete_meetplan", instance.teacher, instance)


class MeetPlanUpdate(ModelUpdateMutation):
class Meta:
model = MeetPlan

@classmethod
def clean_input(cls, info, instance, data):
cleaned_input = super().clean_input(info, instance, data)
user = info.context.user
if user.is_admin:
pass
elif user.is_teacher:
if "teacher" in cleaned_input and cleaned_input["teacher"].id != user.id:
# 教师只能修改自己的安排
raise ValidationError({"teacher": _("You can not update the teacher field.")})
else:
if (
("teacher" in cleaned_input and cleaned_input["teacher"].id != instance.teacher_id)
or ("start_time" in cleaned_input and cleaned_input["start_time"] != instance.start_time)
or ("place" in cleaned_input and cleaned_input["place"] != instance.place)
or ("duration" in cleaned_input and cleaned_input["duration"] != instance.duration)
or ("t_message" in cleaned_input and cleaned_input["t_message"] != instance.t_message)
):
# 学生不能修改安排信息
raise ValidationError({"meet_plan": _("You can not modify meet plan details.")})

if instance.student_id is not None and user.id != instance.student_id:
# 学生不能修改他人的预约信息
raise ValidationError({"student": _("You can not modify this.")})

if instance.student_id is not None and "student" in cleaned_input and cleaned_input["student"] is None:
# 不允许学生自己取消预约
raise ValidationError(
{"student": _("You can not delete your order, please connect the teacher using email or phone.")}
)

if (
instance.student_id is not None
and "student" in cleaned_input
and user.id != cleaned_input["student"].id
):
# 不允许学生将预约信息改成其他人
raise ValidationError({"student": _("You can not change student field to other student.")})

if "student" in cleaned_input and user.id != cleaned_input["student"].id:
# 学生不能帮他人预约信息
raise ValidationError({"student": _("You can not make order for other student.")})

if "complete" in cleaned_input and cleaned_input["complete"] and not instance.complete:
# 学生不能标记预约为已完成
raise ValidationError({"complete": _("You can not change complete field.")})

return cleaned_input

@classmethod
def before_save(cls, info, instance, cleaned_input=None):
user = info.context.user
if user.is_admin:
pass
elif user.is_teacher:
if not user.has_perm("meet_plan.change_meetplan", instance):
raise ValidationError({"teacher": _("You can only update your own meet plan.")})
else:
if instance.start_time < timezone.now():
# 学生不能修改之前的预约信息
raise ValidationError({"start_time": _("You can not change previous plan.")})


class MeetPlanDelete(ModelDeleteMutation):
class Meta:
model = MeetPlan

@classmethod
def get_instance(cls, info, obj_id):
instance = super().get_instance(info, obj_id)
user = info.context.user
if user.is_admin:
pass
elif user.is_teacher:
if not user.has_perm("meet_plan.delete_meetplan", instance):
raise PermissionDenied()
else:
raise PermissionDenied()
if instance.student and instance.complete:
raise PermissionDenied(message=_("This plan should not be deleted as it is completed!"))
return instance


class Mutation(graphene.ObjectType):
# use create instead of update!!!
term_date_update = TermDateCreate.Field()

meet_plan_create = MeetPlanCreate.Field()
meet_plan_update = MeetPlanUpdate.Field()
meet_plan_delete = MeetPlanDelete.Field()
Loading