How to query nested object in GraphQL? - graphql

I am new to GraphQL and MongoDB and try to build a backend using both.
I have followed the sample on https://github.com/graphql-python/graphene-mongo/tree/master/examples/flask_mongoengine and somehow able to build one. But I am not able to query the nested object, I am not sure if it is the issue of my code or my query?
models.py
from mongoengine import connect, Document, EmbeddedDocument
from mongoengine.fields import (
StringField,
IntField,
ReferenceField,
ListField,
EmbeddedDocumentField,
ObjectIdField
)
class author(Document):
meta = {'db_alias': 'db', 'collection': 'author'}
acronym = StringField()
name = StringField()
notes = StringField()
class author_list(EmbeddedDocument):
sequence = IntField()
author = ReferenceField(author)
class book(Document):
meta = {'db_alias': 'db', 'collection': 'book'}
publish_date = StringField()
title = StringField()
author_list = ListField(EmbeddedDocumentField(author_list))
category = StringField()
schema.py
import graphene
from graphene.relay import Node
from graphene_mongo import MongoengineConnectionField, MongoengineObjectType
from models import author as AuthorModel
from models import author_list as AuthorListModel
from models import book as BookModel
class AuthorType(MongoengineObjectType):
class Meta:
model = AuthorModel
interfaces = (Node,)
class AuthorListType(MongoengineObjectType):
class Meta:
model = AuthorListModel
interfaces = (Node,)
class BookType(MongoengineObjectType):
class Meta:
model = BookModel
interfaces = (Node,)
class Query(graphene.ObjectType):
node = Node.Field()
books = MongoengineConnectionField(BookType)
authors = MongoengineConnectionField(AuthorType)
schema = graphene.Schema(query=Query, types=[BookType, AuthorListType, AuthorType])
Query
{
books {
edges {
node {
authorList{
edges {
node {
sequence
author(acronym: "CC") {
name
}
}
}
}
}
}
}
}
And I receive the following error:
{
"errors": [
{
"message": "Unknown argument \"acronym\" on field \"author\" of type \"AuthorListType\".",
"locations": [
{
"line": 10,
"column": 20
}
]
}
]
}

Related

Regroup duplicate values into single key value array in Django Rest Framework?

I am using DRF to create an api:
My Models are:
from django.db import models
class Product(models.Model):
productId = models.CharField(max_length=120, primary_key=True)
productCategory = models.CharField(max_length=120)
name = models.CharField(max_length=120)
insertTime = models.DateField()
def __str__(self):
return self.productId
class LendingRate(models.Model):
productId = models.ForeignKey(Product, related_name="lendingRates", on_delete=models.CASCADE)
rate = models.CharField(max_length=120)
tiersName = models.CharField(max_length=120)
tiersMinimumValue = models.CharField(max_length=120)
tiersMaximumValue = models.CharField(max_length=120)
insertTime = models.DateField()
def __str__(self):
return self.rate
My Serializers are:
from rest_framework import serializers
from .models import Product
from .models import LendingRate
class LendingRateSerializer(serializers.ModelSerializer):
class Meta:
model = LendingRate
fields = ('rate', 'tiersName', 'tiersMinimumValue', 'tiersMaximumValue')
class ProductSerializer(serializers.ModelSerializer):
lendingRates = LendingRateSerializer(many=True)
class Meta:
model = Product
fields = ('productId','productCategory', 'name', 'lendingRates')
This would provide me a response like this:
"data": {
"productId": "62678a1c-daa8-491c-941e-63745620338f",
"productCategory": "RESIDENTIAL_MORTGAGES",
"name": "Fixed Rate Home Loan - 1 Year",
"lendingRates": [
{
"rate": "0.0529",
"tiersName": "LVR",
"tiersMinimumValue": "0.2934",
"tiersMaximumValue": "0.3932"
},
{
"rate": "0.0529",
"tiersName": "LVR",
"tiersMinimumValue": "0.0863",
"tiersMaximumValue": "0.0974"
},
{
"rate": "0.0619",
"tiersName": "LVR",
"tiersMinimumValue": "0.0437",
"tiersMaximumValue": "0.0621"
},
{
"rate": "0.0619",
"tiersName": "LVR",
"tiersMinimumValue": "0.0697",
"tiersMaximumValue": "0.0775"
},
{
"rate": "0.0348",
"tiersName": "LVR",
"tiersMinimumValue": "0.0224",
"tiersMaximumValue": "0.0443"
}
]
}
I am not able to group the rate with same value into an array.
As seen in the api response,I want to group the response with same rate into single array of objects.
For example like this:
desired api response
Here is my views.py
#api_view()
def productDetail(request, productId):
try:
product = Product.objects.get(productId=productId)
except Product.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
productSerializer = ProductSerializer(product, context={'request': request}).data
(don't know how to proceed from here)
.........
....
.
.
.
return Response()
I highly appreciate any help on this.
Thank you.

How to get multiple records in the main model based on an array from a nested serializer?

I have next code:
models.py
class tbox(models.Model):
name = models.CharField("box", max_length=15)
status = models.CharField("status box", max_length=50)
class tbox_move(models.Model):
tbox = models.ForeignKey('api.tbox',related_name='tbox_moves', on_delete=models.DO_NOTHING)
discription = models.TextField(null=True, blank=True)
def __str__(self):
return '%d: %s' % (self.order, self.title)
serializer.py
from rest_framework import serializers
from .models import tbox, tbox_move
class tboxSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, write_only=False)
class Meta:
model = tbox
fields = ['id', 'name', 'status']
def update(self, instance, validated_data):
print("Updade in serializer TBOX")
class tbox_moveSerializer(serializers.ModelSerializer):
tbox_moves = tboxSerializer(many=True)
class Meta:
model = tbox_move
fields = ['tbox_moves', 'discription']
def create(self, validated_data):
tboxs_data = validated_data.pop('tbox_moves')
for tbox_data in tboxs_data:
tbox_id = tbox_data.get("id", None)
if tbox_id is None:
print("requare BOX ID")
id_tbox = tbox.objects.filter(id=tbox_id).update(**tbox_data)
tbox_m = tbox_move.objects.create(tbox=tbox_id, **validated_data)
return tbox_m
I send next request POST json:
{
"tbox_moves" : [
{"id" : 3, "id_test" : 1, "name" : "name_box1", "status": "block"},
{"id" : 1, "id_test" : 1, "name" : "name_box2", "status": "block"}
],
"description": "collective data"
}
I want to get the following result.
Create multiple records in the model equal to the number in the nested serializer
Example tbox_move model:
ID
discription
tbox_id
10
collective data
3
11
collective data
1
I want update status in the nested model tbox from enable to block
How this create?

How do you access nested object information with Django Rest Framework?

I've got two models connected through a ManyToManyField that links projects together with users, as such:
class Project(Model):
STATUS_CHOICES = (
('active', 'Active'),
('archived','Archived'),
)
name = CharField(max_length=50)
members = ManyToManyField("accounts.User", through='ProjectUser')
organization = ForeignKey(Organization, related_name="organizations", on_delete=CASCADE, verbose_name="Team")
status = CharField(max_length=10, choices=STATUS_CHOICES, default='active')
def __str__(self):
return self.name
class Meta:
db_table = 'project'
ordering = ('organization', 'name')
unique_together = ('name', 'organization',)
class ProjectUser(Model):
ROLE_CHOICES = (
('member', 'Member'),
('admin','Admin'),
)
user = ForeignKey("accounts.User", on_delete=CASCADE)
project = ForeignKey(Project, on_delete=CASCADE)
user_hour_cost = DecimalField(max_digits=6, decimal_places=2, default=0)
role = CharField(max_length=10, choices=ROLE_CHOICES, default='member')
class Meta:
db_table = 'projectuser'
ordering = ('user',)
unique_together = ('project', 'user',)
and a ProjectSerializer that looks like this:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ["name", "organization", "members"]
I wish to extract data about the users when using the ProjectSerializer (e.g get the username, email, first name, last name from the User model). All I get back with this serializer is
{
"name": "Project X/Y",
"organization": 1,
"members": [
2,
1
]
}
Is there a way for me to traverse the information on the members so my template can use it? E.g members[0].username?
I can't just use depth = 1 because that returns data directly from User model, but ignores the fields on the ProjectUser model
I'm looking for something along the lines of
{
"name": "Project X/Y AB",
"organization": 1,
"projectusers": [
{
"user": ["id": 1, "username": "foo", "first_name": "joey"],
"project": 1,
"user_hour_cost": "550.00",
"role": "admin"
},
{
"user": ["id": 2, "username": "hellboy", "first_name": "erik"],
"project": 1,
"user_hour_cost": "190.00",
"role": "member"
}
]
}
Doesn't necessarily have to look just like this - but I need for my frontend to receive information about the user that sits on the User table in my db
Maybe you could try to specify your own serializer for the (project)users. This is covered more in depth in the official DRF docs.
class ProjectSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True)
class Meta:
model = Project
fields = ["name", "organization", "members"]
and define your Member and User Serializer:
class MemberSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = ProjectMember
fields = ["user ", "...", "role "]
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "first_name", "..."]
But beware that making such a construct writeable is tricky. You would probably have to overwrite your Serializers create() methods to implement this. See here for more details.
I actually solved it by just nesting another serialized object inside ProjectUser
User Serializer
from accounts.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "first_name", "email")
and then
from .models import Project
from .models import ProjectUser
from accounts.serializers import UserSerializer
from rest_framework import serializers
class ProjectUserSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = ProjectUser
fields = ("user", "user_hour_cost", "role")
class ProjectSerializer(serializers.ModelSerializer):
projectusers = ProjectUserSerializer(many=True, read_only=True)
class Meta:
model = Project
fields = ["name", "organization", "projectusers"]
Which returned
{
"name": "Project XXAA",
"organization": 1,
"projectusers": [
{
"user": {
"username": "Google",
"first_name": "Chrome",
"email": "google#chrome.com"
},
"user_hour_cost": "550.00",
"role": "admin"
},
{
"user": {
"username": "Mozilla",
"first_name": "Joey",
"email": "mozilla#firefox.com"
},
"user_hour_cost": "190.00",
"role": "member"
}
]
}
Good enough to work with!

Return any data from a query using GraphQL, Graphene and Python

I am receiving the following error:
{
"errors": [
{
"message": "Unknown argument \"project_id\" on field" +
\"get_project_detail_summary\" of type \"Query\".",
"locations": [
{
"line": 2,
"column": 30
}
]
}
]
}
With the following query:
query GetProjectDetailSummary($project_id: Int) {
get_project_detail_summary(project_id: $project_id) {
comments {
... on ManagerCommentNode {
id
text
created
}
... on VendorCommentNode {
id
text
created
}
... on TenantCommentNode {
id
text
created
}
}
}
}
With the following backend code, How can I get to the breakpoint?, or how do I send back custom data given a number?:
class CommentsUnion(graphene.types.union.Union):
class Meta:
name = 'CommentsUnion'
types = (ManagerCommentNode, VendorCommentNode, TenantCommentNode, )
class ProjectSummaryInput(graphene.InputObjectType):
project_id = graphene.Int()
class ProjectSummaryNode(graphene.ObjectType):
Input = ProjectSummaryInput
project_id = graphene.Int()
comments = graphene.List(CommentsUnion)
#classmethod
def resolve_comments(self, *args, **kwargs):
import pdb;pdb.set_trace()
return ProjectSummary.select_related('comments').objects.filter(comments__created__lt=dt)
class Query(graphene.ObjectType):
get_project_detail_summary = Field(ProjectSummaryNode)
In regards to the error.
Be sure to add a kwarg ( e.g. project_id in this example, it is the reason for the "unknown argument on field" error ) to the graphene.Field for get_project_detail_summary.
Like so:
class Query(graphene.ObjectType):
get_project_detail_summary = Field(ProjectSummaryNode, # see below for example
project_id=graphene.Int() # kwarg here
)
def resolve_get_project_detail_summary(self, info, **kwargs):
return ProjectSummary.objects.get(id=kwargs.get('project_id'))
In regards to returning any data.
This is a way ( returning a graphene.String ), but it untypes the response by putting everything inside a String:
from django.core.serializers.json import DjangoJSONEncoder
class ProjectSummaryRecentUpdatesNode(graphene.ObjectType):
Input = ProjectSummaryInput
recent_updates = graphene.String()
def resolve_recent_updates(self, resolve, **kwargs):
instance = Project.objects.get(id=resolve.variable_values.get("project_id"))
things = instance.things.all()
# these are all from different models, and the list is a bit longer than this.
querysets = (
("scheduled", get_scheduled(things, resolve, **kwargs)),
("completed", get_completed(things, resolve, **kwargs)),
("invoices", get_invoices(things, resolve, **kwargs)),
("expenditures", get_expenditures(things, resolve, **kwargs)),
("comments", get_comments(things, resolve, **kwargs)),
("files", get_files(things, resolve, **kwargs)),
)
recent_updates = []
for update_type, qs in querysets:
for item in qs:
item.update(
{
"recent_update_type": update_type
}
)
recent_updates.append(item)
return json.dumps(recent_updates, cls=DjangoJSONEncoder)
And on the frontend, with an import { Query } from "react-apollo"; element we can JSON.parse the field ... which has "any" (json serialized) data inside of it:
<Query
query={GET_PROJECT_DETAIL_SUMMARY_RECENT_UPDATES}
fetchPolicy="network-only"
variables={{ project_id: this.props.projectId }}
>
{({ loading, error, data }: QueryResult) => {
if (
data &&
data.get_project_detail_summary_recent_updates &&
data.get_project_detail_summary_recent_updates.recent_updates
) {
console.log(JSON.parse(data.get_project_detail_summary_recent_updates.recent_updates))
}
}}
</Query>
Side note:
If there isn't a large list of data types create a Union like object which has all of the fields needed from different models, or an actual Union, which seems like the correct way, as then types are not lost:
class ProjectSummaryNode(graphene.ObjectType):
# from FileNode graphene.ObjectType class
file__id = graphene.Int()
file__brief_description = graphene.String()
file_count = graphene.Int()
last_file_created = graphene.String()
last_file = graphene.String()
# from UploaderNode graphene.ObjectType class
uploader__first_name = graphene.String()
uploader__last_name = graphene.String()
# from CommentNode graphene.ObjectType class
comment = graphene.String()
class Meta:
name = "ProjectSummaryNode"
# example of Union, the above fields would be commented out, as they are part of
# the different graphene.ObjectType classes, and this line below would be uncommented
# types = ( FileNode, UploaderNode, CommentNode, )
Still open to suggestions on a better way to do this.

How to write an API that allows PUT value in the url itself

I have the below classes:
Models.py
class User(models.Model):
name = models.CharField(mex_length=50)
email = models.EmailField(max_length=100)
department = models.CharField(max_length=50)
def __str__(self):
return self.name
Views.py
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
#action(methods=['PUT'], detail=True)
def update_department(self, request, *args, **kwargs)
obj = self.get_object()
??
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('department', )
Now I have 2 users in my database:
{
"id": 1,
"name": "abcd",
"email": "abcd#gmail.com",
"department": "accounts"
}
{
"id": 2,
"name": "efgh",
"email": "efgh#gmail.com",
"department": "HR"
}
I want to update the department for user 2 to "accounts" with an api as below where I pass the value to be update with the api url itself:
PUT "/api/user/2/update_department/accounts"
How can this be achieved with DRF?
Urls.py
from django.conf.urls import url
from django.conf.urls import include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'user', views.UserViewSet, base_name='user_view')
slashless_router = routers.DefaultRouter(trailing_slash=False)
slashless_router.registry = router.registry[:]
urlpatterns = [
url(r'', include(router.urls)),
url(r'', include(slashless_router.urls)),
]

Resources