From 485a6ca4eb6bab90cebec3a5693a47540f074d18 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 7 Mar 2019 10:49:52 +0100 Subject: [PATCH] Add portfolio backend --- .../rooms/NewRoom.vue => pages/newRoom.vue} | 0 server/api/schema.py | 6 ++- server/api/utils.py | 12 ++++++ server/core/settings.py | 1 + server/portfolio/__init__.py | 0 server/portfolio/inputs.py | 17 ++++++++ server/portfolio/migrations/0001_initial.py | 29 ++++++++++++++ server/portfolio/migrations/__init__.py | 0 server/portfolio/models.py | 10 +++++ server/portfolio/mutations.py | 40 +++++++++++++++++++ server/portfolio/schema.py | 31 ++++++++++++++ server/portfolio/serializers.py | 10 +++++ server/rooms/schema.py | 11 +---- 13 files changed, 156 insertions(+), 11 deletions(-) rename client/src/{components/rooms/NewRoom.vue => pages/newRoom.vue} (100%) create mode 100644 server/portfolio/__init__.py create mode 100644 server/portfolio/inputs.py create mode 100644 server/portfolio/migrations/0001_initial.py create mode 100644 server/portfolio/migrations/__init__.py create mode 100644 server/portfolio/models.py create mode 100644 server/portfolio/mutations.py create mode 100644 server/portfolio/schema.py create mode 100644 server/portfolio/serializers.py diff --git a/client/src/components/rooms/NewRoom.vue b/client/src/pages/newRoom.vue similarity index 100% rename from client/src/components/rooms/NewRoom.vue rename to client/src/pages/newRoom.vue diff --git a/server/api/schema.py b/server/api/schema.py index 97ba521f..dc4d40ec 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -13,20 +13,22 @@ from books.schema.queries import BookQuery from core.schema.mutations.main import CoreMutations from objectives.mutations import ObjectiveMutations from objectives.schema import ObjectivesQuery +from portfolio.mutations import PortfolioMutations +from portfolio.schema import PortfolioQuery from rooms.mutations import RoomMutations from rooms.schema import RoomsQuery from users.schema import UsersQuery class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery, - BasicKnowledgeQuery, graphene.ObjectType): + BasicKnowledgeQuery, PortfolioQuery, graphene.ObjectType): node = relay.Node.Field() if settings.DEBUG: debug = graphene.Field(DjangoDebug, name='__debug') -class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, +class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations, graphene.ObjectType): if settings.DEBUG: debug = graphene.Field(DjangoDebug, name='__debug') diff --git a/server/api/utils.py b/server/api/utils.py index 3642bc2c..6cd70ac4 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -1,6 +1,7 @@ import io import os +from django.apps import apps from graphene_django.filter import DjangoFilterConnectionField from graphql_relay.node.node import from_global_id @@ -53,3 +54,14 @@ def get_graphql_mutation(filename): mutation = f.read() return mutation + + +def get_by_id_or_slug(model, **kwargs): + slug = kwargs.get('slug') + id = kwargs.get('id') + + if id is not None: + return get_object(model, id) + if slug is not None: + return model.objects.get(slug=slug) + return None diff --git a/server/core/settings.py b/server/core/settings.py index 9a4c40c7..2791d709 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ 'rooms', 'assignments', 'basicknowledge', + 'portfolio', 'statistics', 'wagtail.contrib.forms', diff --git a/server/portfolio/__init__.py b/server/portfolio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/portfolio/inputs.py b/server/portfolio/inputs.py new file mode 100644 index 00000000..591fa6d2 --- /dev/null +++ b/server/portfolio/inputs.py @@ -0,0 +1,17 @@ +import graphene +from graphene import InputObjectType + + +class ProjectInput(InputObjectType): + title = graphene.String() + description = graphene.String() + objectives = graphene.String() + appearance = graphene.String() + + +class AddProjectArgument(ProjectInput): + pass + + +class UpdateProjectArgument(ProjectInput): + id = graphene.ID(required=True) diff --git a/server/portfolio/migrations/0001_initial.py b/server/portfolio/migrations/0001_initial.py new file mode 100644 index 00000000..b1700bf6 --- /dev/null +++ b/server/portfolio/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.6 on 2019-03-06 14:47 + +from django.db import migrations, models +import django_extensions.db.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='title')), + ('description', models.TextField(blank=True, null=True, verbose_name='description')), + ('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from='title', verbose_name='slug')), + ('objectives', models.TextField(blank=True)), + ('appearance', models.CharField(blank=True, max_length=255)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/server/portfolio/migrations/__init__.py b/server/portfolio/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/portfolio/models.py b/server/portfolio/models.py new file mode 100644 index 00000000..c02870dc --- /dev/null +++ b/server/portfolio/models.py @@ -0,0 +1,10 @@ +from django.db import models +from django_extensions.db.models import TitleSlugDescriptionModel + + +class Project(TitleSlugDescriptionModel): + objectives = models.TextField(blank=True) + appearance = models.CharField(blank=True, null=False, max_length=255) + + def __str__(self): + return self.title diff --git a/server/portfolio/mutations.py b/server/portfolio/mutations.py new file mode 100644 index 00000000..6c577a25 --- /dev/null +++ b/server/portfolio/mutations.py @@ -0,0 +1,40 @@ +import graphene +from graphene import relay + +from api.utils import get_object +from portfolio.inputs import AddProjectArgument, UpdateProjectArgument +from portfolio.models import Project +from portfolio.schema import ProjectNode +from portfolio.serializers import ProjectSerializer + + +class MutateProject(relay.ClientIDMutation): + errors = graphene.List(graphene.String) + project = graphene.Field(ProjectNode) + + @classmethod + def mutate_and_get_payload(cls, *args, **kwargs): + project_data = kwargs.get('project') + if project_data.get('id') is not None: + project = get_object(Project, project_data['id']) + serializer = ProjectSerializer(project, data=project_data) + else: + serializer = ProjectSerializer(data=project_data) + if serializer.is_valid(): + serializer.save() + return cls(project=serializer.instance, errors=None) + + return cls(project=None, errors=['{}: {}'.format(key, value) for key, value in serializer.errors.items()]) + + +class AddProject(MutateProject): + class Input: + project = graphene.Argument(AddProjectArgument) # NB: can't be named AddProjectInput, otherwise graphene complains + +class UpdateProject(MutateProject): + class Input: + project = graphene.Argument(UpdateProjectArgument) + +class PortfolioMutations: + add_project = AddProject.Field() + update_project = UpdateProject.Field() diff --git a/server/portfolio/schema.py b/server/portfolio/schema.py new file mode 100644 index 00000000..e31bb156 --- /dev/null +++ b/server/portfolio/schema.py @@ -0,0 +1,31 @@ +import graphene +from graphene import relay +from graphene_django import DjangoObjectType +from graphene_django.filter import DjangoFilterConnectionField + +from api.utils import get_by_id_or_slug +from portfolio.models import Project + + +class ProjectNode(DjangoObjectType): + pk = graphene.Int() + + class Meta: + model = Project + filter_fields = ['slug', 'appearance'] + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + +class PortfolioQuery(object): + project = graphene.Field(ProjectNode, id=graphene.ID(), slug=graphene.String()) + + projects = DjangoFilterConnectionField(ProjectNode) + + def resolve_projects(self, info, **kwargs): + return Project.objects.all() + + def resolve_project(self, info, **kwargs): + return get_by_id_or_slug(Project, **kwargs) diff --git a/server/portfolio/serializers.py b/server/portfolio/serializers.py new file mode 100644 index 00000000..36eaaae1 --- /dev/null +++ b/server/portfolio/serializers.py @@ -0,0 +1,10 @@ +from rest_framework import serializers + +from portfolio.models import Project + + +class ProjectSerializer(serializers.ModelSerializer): + class Meta: + model = Project + fields = ('id', 'title', 'description', 'objectives', 'slug', 'appearance',) + read_only_fields = ('id', 'slug',) diff --git a/server/rooms/schema.py b/server/rooms/schema.py index b40cf50c..e224a5ec 100644 --- a/server/rooms/schema.py +++ b/server/rooms/schema.py @@ -5,7 +5,7 @@ from graphene import relay from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField -from api.utils import get_object +from api.utils import get_object, get_by_id_or_slug from rooms.models import Room, RoomEntry from users.schema import UserNode @@ -55,14 +55,7 @@ class RoomsQuery(object): return Room.objects.filter(school_class__in=user.school_classes.all()) def resolve_room(self, info, **kwargs): - slug = kwargs.get('slug') - room_id = kwargs.get('id') - - if room_id is not None: - return get_object(Room, room_id) - if slug is not None: - return Room.objects.get(slug=slug) - return None + return get_by_id_or_slug(Room, **kwargs) def resolve_room_entry(self, info, **kwargs): slug = kwargs.get('slug')