diff --git a/server/portfolio/inputs.py b/server/portfolio/inputs.py index 591fa6d2..584b8018 100644 --- a/server/portfolio/inputs.py +++ b/server/portfolio/inputs.py @@ -15,3 +15,17 @@ class AddProjectArgument(ProjectInput): class UpdateProjectArgument(ProjectInput): id = graphene.ID(required=True) + + +class ProjectEntryInput(InputObjectType): + activity = graphene.String() + reflection = graphene.String() + next_steps = graphene.String() + + +class AddProjectEntryArgument(ProjectEntryInput): + project = graphene.ID(required=True) + + +class UpdateProjectEntryArgument(ProjectEntryInput): + id = graphene.ID(required=True) diff --git a/server/portfolio/migrations/0002_projectentry.py b/server/portfolio/migrations/0002_projectentry.py new file mode 100644 index 00000000..62f5651a --- /dev/null +++ b/server/portfolio/migrations/0002_projectentry.py @@ -0,0 +1,25 @@ +# Generated by Django 2.0.6 on 2019-03-12 08:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('portfolio', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectEntry', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('activity', models.TextField(blank=True)), + ('reflection', models.TextField(blank=True)), + ('next_steps', models.TextField(blank=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='entries', to='portfolio.Project')), + ], + ), + ] diff --git a/server/portfolio/models.py b/server/portfolio/models.py index c02870dc..32ca421b 100644 --- a/server/portfolio/models.py +++ b/server/portfolio/models.py @@ -8,3 +8,14 @@ class Project(TitleSlugDescriptionModel): def __str__(self): return self.title + + +class ProjectEntry(models.Model): + activity = models.TextField(blank=True) + reflection = models.TextField(blank=True) + next_steps = models.TextField(blank=True) + created = models.DateTimeField(auto_now_add=True) + project = models.ForeignKey(Project, related_name='entries', on_delete=models.CASCADE) + + def __str__(self): + return self.activity diff --git a/server/portfolio/mutations.py b/server/portfolio/mutations.py index 6c577a25..bbb6ebc6 100644 --- a/server/portfolio/mutations.py +++ b/server/portfolio/mutations.py @@ -2,39 +2,108 @@ 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 +from portfolio.inputs import AddProjectArgument, UpdateProjectArgument, AddProjectEntryArgument, \ + UpdateProjectEntryArgument +from portfolio.models import Project, ProjectEntry +from portfolio.schema import ProjectNode, ProjectEntryNode +from portfolio.serializers import ProjectSerializer, ProjectEntrySerializer + + +# class Mutation(relay.ClientIDMutation): +# class Meta: +# pass +# +# @classmethod +# def mutate_and_get_payload(cls, *args, **kwargs): +# data = kwargs.get(cls.meta.property) +# if data.get('id') is not None: +# project = get_object(cls.meta.serializer_class.model, data['id']) +# serializer = cls.meta.serializer_class(project, data=data) +# else: +# serializer = cls.meta.serializer_class(data=data) +# if serializer.is_valid(): +# serializer.save() +# props = { +# cls.meta.property: serializer.instance, +# 'errors': None +# } +# return cls(**props) +# +# return cls(errors=['{}: {}'.format(key, value) for key, value in serializer.errors.items()]) class MutateProject(relay.ClientIDMutation): errors = graphene.List(graphene.String) project = graphene.Field(ProjectNode) + # class Meta: + # property = 'project' + # serializer_class = ProjectSerializer + @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) + data = kwargs.get('project') + if data.get('id') is not None: + entity = get_object(Project, data['id']) + serializer = ProjectSerializer(entity, data=data) else: - serializer = ProjectSerializer(data=project_data) + serializer = ProjectSerializer(data=data) if serializer.is_valid(): serializer.save() - return cls(project=serializer.instance, errors=None) + props = { + 'project': serializer.instance, + 'errors': None + } + return cls(**props) - return cls(project=None, errors=['{}: {}'.format(key, value) for key, value in serializer.errors.items()]) + return cls(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 + project = graphene.Argument( + AddProjectArgument) # NB: can't be named AddProjectInput, otherwise graphene complains + class UpdateProject(MutateProject): class Input: project = graphene.Argument(UpdateProjectArgument) + +class MutateProjectEntry(relay.ClientIDMutation): + errors = graphene.List(graphene.String) + project_entry = graphene.Field(ProjectEntryNode) + + @classmethod + def mutate_and_get_payload(cls, *args, **kwargs): + data = kwargs.get('project_entry') + + if data.get('project') is not None: + data['project'] = get_object(Project, data.get('project')).id + if data.get('id') is not None: + entity = get_object(ProjectEntry, data['id']) + serializer = ProjectEntrySerializer(entity, data=data, partial=True) + else: + serializer = ProjectEntrySerializer(data=data) + if serializer.is_valid(): + serializer.save() + return cls(project_entry=serializer.instance, errors=None) + + return cls(project_entry=None, errors=['{}: {}'.format(key, value) for key, value in serializer.errors.items()]) + + +class AddProjectEntry(MutateProjectEntry): + class Input: + project_entry = graphene.Argument(AddProjectEntryArgument) + + +class UpdateProjectEntry(MutateProjectEntry): + class Input: + project_entry = graphene.Argument(UpdateProjectEntryArgument) + + class PortfolioMutations: add_project = AddProject.Field() update_project = UpdateProject.Field() + add_project_entry = AddProjectEntry.Field() + update_project_entry = UpdateProjectEntry.Field() diff --git a/server/portfolio/schema.py b/server/portfolio/schema.py index a60f5dfa..2edf9f91 100644 --- a/server/portfolio/schema.py +++ b/server/portfolio/schema.py @@ -4,7 +4,7 @@ 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 +from portfolio.models import Project, ProjectEntry class ProjectNode(DjangoObjectType): @@ -19,9 +19,14 @@ class ProjectNode(DjangoObjectType): return self.id +class ProjectEntryNode(DjangoObjectType): + class Meta: + model = ProjectEntry + interfaces = (relay.Node,) + + class PortfolioQuery(object): project = graphene.Field(ProjectNode, id=graphene.ID(), slug=graphene.String()) - projects = DjangoFilterConnectionField(ProjectNode) def resolve_projects(self, info, **kwargs): diff --git a/server/portfolio/serializers.py b/server/portfolio/serializers.py index 36eaaae1..ed186aa8 100644 --- a/server/portfolio/serializers.py +++ b/server/portfolio/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from portfolio.models import Project +from portfolio.models import Project, ProjectEntry class ProjectSerializer(serializers.ModelSerializer): @@ -8,3 +8,10 @@ class ProjectSerializer(serializers.ModelSerializer): model = Project fields = ('id', 'title', 'description', 'objectives', 'slug', 'appearance',) read_only_fields = ('id', 'slug',) + + +class ProjectEntrySerializer(serializers.ModelSerializer): + class Meta: + model = ProjectEntry + fields = ('id', 'activity', 'reflection', 'next_steps', 'created', 'project') + read_only_fields = ('id', 'created',)