Django + Graphene + DjangoModelFormMutation : dissociating create and update - django

I am using a DjangoModelFormMutation from Graphene-python to expose a generic mutation for a model, like so :
from graphene import Field
from graphene_django.forms.mutation import DjangoModelFormMutation
from django.db import models
class Pet(models.Model):
name = models.CharField()
legs = models.PositiveIntegerField()
class PetForm(forms.ModelForm):
class Meta:
model = Pet
fields = ('name', 'legs')
# This will get returned when the mutation completes successfully
class PetType(DjangoObjectType):
class Meta:
model = Pet
class PetMutation(DjangoModelFormMutation):
pet = Field(PetType)
class Meta:
form_class = PetForm
Then in my schema file, I register the mutation like this :
create_pet = mutations.PetMutation.Field()
update_pet = mutations.PetMutation.Field()
This works, but there are two desired behaviors I can't achieve this way :
Have two different mutations : one for create that can only create and not update, and one for update that can only update, and not create.
Have the update mutation work partially as well , i.e only updating the client-provided fields.
How can I achieve the desired behavior ?

Related

Avoid nested objects when using nested serializers

I have two models, one contains the other in a foreignKey relationship, I wanted to make an API that would return the combine of these two models, so I attempted to use nested Serializers to add the related model as well, but the data are not all on the same level, the related models is a object inside the first.
Here are the Models
class ModelOne(models.Model):
last_counter = models.IntegerField()
class ModelTwo(models.Model):
model_one = models.ForeignKey(ModelOne, on_delete=models.CASCADE)
category = models.CharField(max_length=64)
counter_type = models.CharField(max_length=32)
Here are the serializers
class ModelOneSerializer(serializers.ModelSerializer):
class Meta:
model = ModelOne
fields = "__all__"
class ModelTwoSerializer(serializers.ModelSerializer):
model_one= ModelOneSerializer(read_only=True)
class Meta:
model = ModelTwo
fields = "__all__"
This would return from the API in the form of
{
"category" : ...,
"counter_type" : ...,
"model_one" : {
"last_counter" : ...
}
}
But I don't want the response to be like that, I want it more like this
{
"category" : ...,
"counter_type" : ...,
"last_counter" : ...,
}
Is there a way to achieve this through serializers?
Use SerializerMethodField
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = SerializerMethodField()
class Meta:
model = ModelTwo
fields = "__all__"
def get_last_counter(self, obj):
return ModelOneSerializer(obj.model_one).data['last_counter']
When creating custom fields(field_one for example) with SerializerMethodField, you have to create a method called get_field_one, for this method to be automatically detected by the serializer.
You can achieve what you want to do using SerializerMethodField from drf fields:
SerializerMethodField is a read-only field that computes its value at request processing time, by calling a method on the serializer class it is attached to. For example for your case it will look like this. Notice that the computed last_counter is added on the serialized model fields.
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = serializers.SerializerMethodField()
class Meta:
model = ModelTwo
fields = ["category", "counter_type", "last_counter"]
def get_last_counter(self, obj):
return int(obj.model_one.last_counter)
SerializerMethodField accepts method_name, but it’s usually more convenient to use the default pattern for naming those methods, which is get_. Just make sure you‘re not overburdening your method fields with any heavy-lifting operations.
You can read more on the official documentation:enter link description here

How to specify which value is returned for a foreign field when serialising data in a view in Django

I have a model containing a ForeignKey to another model. I am attempting to serialize this model and want to control what field is returned for the foreignkey field. See below:
models.py
class Surveyor(models.Model):
num = models.CharField(max_length=3)
name = models.CharField(max_length=250)
class Anblsrecord(models.Model):
...
sur_num = models.ForeignKey(Surveyor, on_delete=models.CASCADE)
views.py
def anbls_points(request):
points_as_geojson = serialize('geojson', Anblsrecord.objects.all()[:5], fields=(... 'sur_num'))
return JsonResponse(json.loads(points_as_geojson))
When I view this I get:
... "sur_num": 1 ...
where the "1" is "num" from Surveyor class. I want to return "name".
I looked at https://docs.djangoproject.com/en/2.2/topics/serialization/ which talks about multi-table inheritance, but I can't find anything for a related table.
Any help would be greatly appreciated.
Django Rest Framework serializers with django-rest-framework-gis worked:
serializers.py
from anblsrecords import models
from rest_framework_gis.serializers import GeoFeatureModelSerializer
class AnblsrecordSerializer(GeoFeatureModelSerializer):
sur_name = serializers.CharField(source='sur_num.name')
class Meta:
model = models.Anblsrecord
geo_field = "geom"
fields = (
...
'sur_name',
)
views.py
from rest_framework import generics
class ListAnbls_points(generics.ListCreateAPIView):
queryset = Anblsrecord.objects.all()[:5]
serializer_class = serializers.AnblsrecordSerializer
This returns:
"properties": {
...,
"sur_name": "Name of Surveyor",...}, and includes the geometry feature.

Update three-level nested django model using serializer

I am trying to update one of my models (which is a nested model - three level actually as you can see below) and I am getting the following error:
AssertionError: The .update() method does not support writable nestedfields by default. Write an explicit .update() method for serializer SystemSettingsSerializer, or set read_only=True on nested serializer fields.
All day I have been reading about nested models and nested serializers, trying to add update and create methods setting fields as read_only=True but no matter what I did, it just didn't work :( :(
These are my models:
class SystemSettings(models.Model):
# ... some fields
class Components(models.Model):
settings = models.ForeignKey(SystemSettings, related_name="Components")
class SysComponent(models.Model):
class Meta:
abstarct = True
index = models.PositiveIntegerField(primery_key=True)
is_active = models.BooleanField(default=False)
component = NotImplemented
class Foo(SysComponent):
component = models.ForeignKey(Components, related_name="Foo")
class Bar(SysComponent):
component = models.ForeignKey(Components, related_name="Bar")
task_id = models.PositiveIntegerField(default=0)
and serializers:
class SystemSettingsSerializer(ModelSerializer):
Components = ComponentsSerializer(many=True)
class Meta:
model = SystemSettings
fields = [# some fields,
Components]
class ComponentsSerializer(ModelSerializer):
Foo = FooSerializer(many=True)
Bar = BarSerializer(many=True)
class Meta:
model = Components
fields = ['Foo',
'Bar']
class FooSerializer(ModelSerializer):
class Meta:
model = Foo
class BarSerializer(ModelSerializer):
class Meta:
model = Bar
My logic is the following:
I am fetching the SystemSettings via GET and display it in a form.
The user changes it as much as he want and by clicking submit I send it back via PUT.
As I said I am getting the error above after clicking submit.
Any help would be appreciated.
EDIT: I am using django 1.7.8 by the way

Serialize relation both ways with Django rest_framework

I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.

Posting data to create related Tastypie resources simultaneously?

Given two related Django models A and B in a OneToMany relationship:
models.py
class A(models.Model):
name = models.CharField(max_length=5)
class B(models.Model):
name = models.CharField(max_length=5)
a = models.ForeignKey(A)
And given (potentially non-optimal) Tastypie resources:
api.py
class AResource(ModelResource):
bs = fields.ToManyField( 'projectname.api.BResource', 'bs', full = True)
class Meta:
queryset = A.objects.all()
class BResource(ModelResource):
a = fields.ToOneField( AResource, 'a', full = True)
class Meta:
queryset = B.objects.all()
Let's assume the database is empty so far. Now I have related external data, and would like to crowd the database it with both an instance of A and several instances of B.
What is the prettiest Tastypionic way to approach this problem? Is it possible to crowd both A and the Bs at once? Or do I need to crowd first A, and then crowd B supplying A's ID as the ForeignKey?
It would be great if someone could come up with an post example (using e.g. a python dictionary and httplib2, or curl).
Thanks a million.
The solution is here . Use the related name for tastypie fields which automatically populate the reverse relationship while creating multiple objects at once.
http://django-tastypie.readthedocs.org/en/v0.10.0/fields.html#tastypie.fields.RelatedField.related_name
RelatedField.related_name
Used to help automatically populate reverse relations when creating data. Defaults to None.
In order for this option to work correctly, there must be a field on the other Resource with this as an attribute/instance_name. Usually this just means adding a reflecting ToOneField pointing back.
Example:
class EntryResource(ModelResource):
authors = fields.ToManyField('path.to.api.resources.AuthorResource', 'author_set', related_name='entry')
class Meta:
queryset = Entry.objects.all()
resource_name = 'entry'
class AuthorResource(ModelResource):
entry = fields.ToOneField(EntryResource, 'entry')
class Meta:
queryset = Author.objects.all()
resource_name = 'author'
Use of related_name do the task. it maps the objects of related fields and automatically populates the relations when creating data.
as you did full=True on both side of your resources it will generate maximum recursion depth exceeded exception because both resources are full in each others.
Here is one solution involving ManyToMany instead of OneToMany relationships:
models.py
class B(models.Model):
name = models.CharField(max_length=5)
class A(models.Model):
name = models.CharField(max_length=5)
bs = models.ManyToManyField(B)
api.py
class BResource(ModelResource):
class Meta:
queryset = B.objects.all()
resource_name = 'b'
class AResource(ModelResource):
bs = fields.ToManyField( BResource, 'bs', related_name = 'a', full = True, null=True)
class Meta:
queryset = A.objects.all()
resource_name = 'a'
curl
curl -v -H "Content-Type: application/json" -X POST --data '{"name":"a_name1", "bs":[{"name":"b_name1"}, {"name": "b_name2"}]}' http:<my_path>/api/a/
httplib2.py
A working example to post data via a python script using the httplib2 package is based on a neat and simple solution posted by warren-runk:
post_dict(
url='http:<my_path>/api/a/',
dictionary={
'name' : 'a_name1',
'bs' : [
{'name' : 'b_name1'},
{'name' : 'b_name1'},
]
}
)
However, now an additional table to relate A and B is created in the database. There might be better solutions based on the OneToMany relationship of A and B?