strawberry-graphql / strawberry-django Goto Github PK
View Code? Open in Web Editor NEWStrawberry GraphQL Django extension
Home Page: https://strawberry.rocks/docs/django
License: MIT License
Strawberry GraphQL Django extension
Home Page: https://strawberry.rocks/docs/django
License: MIT License
It appears that many-to-many relationships are not supported while using partial update mutation fields. An error is raised saying that DjangoUpdateMutation
does not have the is_relation
attribute.
With the following models, types, etc.:
# models.py
class Fruit(models.Model):
colors = models.ManyToManyField("Color")
class Color(models.Model):
name = models.CharField(max_length=20)
# types.py
@strawberry_django.type(Fruit)
class FruitType:
id: auto
colors: auto
@strawberry_django.input(Color)
class ColorInput:
name: auto
@strawberry_django.input(Fruit, partial=True)
class FruitPartialInput:
id: auto
colors: List[ColorInput]
# schema.py
@strawberry.type
class Query:
fruits: FruitType = strawberry.django.field()
@strawberry.type
class Mutation:
update_fruit: FruitType = strawberry_django.mutations.update(FruitPartialInput)
schema = strawberry.Schema(query=Query, mutation=Mutation)
i get this error (and stacktrace):
Exception in thread django-main-thread:
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/graphql/type/definition.py", line 767, in fields
fields = resolve_thunk(self._fields)
File "/usr/local/lib/python3.10/site-packages/graphql/type/definition.py", line 296, in resolve_thunk
return thunk() if callable(thunk) else thunk
File "/usr/local/lib/python3.10/site-packages/strawberry/schema/schema_converter.py", line 296, in get_graphql_fields
graphql_fields[field_name] = self.from_field(field)
File "/usr/local/lib/python3.10/site-packages/strawberry/schema/schema_converter.py", line 152, in from_field
for argument in field.arguments:
File "/usr/local/lib/python3.10/site-packages/strawberry_django/mutations/fields.py", line 38, in arguments
return arguments + super().arguments
File "/usr/local/lib/python3.10/site-packages/strawberry_django/filters.py", line 148, in arguments
if self.is_relation is False:
AttributeError: 'DjangoUpdateMutation' object has no attribute 'is_relation'
The auto
type hint works fine if I don't use the __future__
library. If I use it it throws this error:
Exception in thread django-main-thread:
Traceback (most recent call last):
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/graphql/type/definition.py", line 767, in fields
fields = resolve_thunk(self._fields)
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/graphql/type/definition.py", line 296, in resolve_thunk
return thunk() if callable(thunk) else thunk
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/strawberry/schema/schema_converter.py", line 297, in get_graphql_fields
graphql_fields[field.graphql_name] = self.from_field(field)
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/strawberry/schema/schema_converter.py", line 167, in from_field
field_type = self.get_graphql_type_field(field)
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/strawberry/schema/schema_converter.py", line 88, in get_graphql_type_field
graphql_type = self.get_graphql_type(field.type)
File "/home/ton/Codes/strawberry-graphql-django/venv/lib/python3.8/site-packages/strawberry/schema/schema_converter.py", line 112, in get_graphql_type
raise TypeError(f"Unexpected type '{type_}'")
TypeError: Unexpected type '<class 'strawberry_django.fields.types.auto'>'
Is there any way to abstract this plugin or it needs to be rewritten for support for Tortoise ORM?
strawberry-graphql/strawberry#1044 introduced some backward incompatible changes, messing with this lib.
No easy fix comes to mind so far.
In the meantime, maybe pinning strawberry <= 0.68
and releasing a new minor version can mitigate.
I noticed there's auth mutations for login
and logout
, but there doesn't seem to be any to register a new user. I think there should be a register
mutation that performs a django validate_password
, and let the developer choose between the usual create
mutation and the register
one.
How to solve problem with m2m fields and circular import? is there any way for "lazy" type declaration?
Maybe its an idea to take (optional) pagination in mind. At the root query, but also at related queries.
This is what I'm using now: https://gist.github.com/joeydebreuk/2e1333fb8da82220bca5300ee81d225c
In create mutation, userId
should be optional. The default value is the current login user‘s id. only the super user has the right to set it.
In update and delete mutation, users can only modify or delete the foreign key value equal to user's id, except super user.
Would it make sense to define all fields always in class? I had also few other improvement ideas for authentication and filters. We have functional implementation in feature/class-api
branch which is still under development.
First draft of doc is available, please take a look and leave your comments. https://github.com/strawberry-graphql/strawberry-graphql-django/blob/feature/class-api/docs/index.md
Changelog:
types
import strawberry_django
from strawberry_django import auto
@strawberry_django.type(models.User)
class User:
id: auto
name: auto
car: List['Car'] = strawberry_django.field(field_name='car_set')
@strawberry_django.type(models.Car)
class Car:
id: auto
model: auto
owner: User
@strawberry_django.input(models.User)
class UserInput:
name: auto
# type inheritance
@strawberry_django.input(models.User, partial=True)
class UserPartialInput(UserInput):
pass
queries and mutations
from strawberry_django import mutations
@strawberry.type
class Query:
user: User = strawberry_django.field()
users: List[User] = strawberry_django.field(filters=UserFilter)
cars: List[Car] = strawberry_django.field(filters=CarFilter, pagination=True)
@strawberry.type
class Mutation:
create_user: User = mutations.create(UserInput)
update_user: User = mutations.update(UserPartialInput)
update_users: List[User] = mutations.update(UserPartialInput, filters=UserFilter, many=True)
delete_users: List[User] = mutations.delete(filters=UserFilter, many=True)
authentication
from strawberry_django import auth
@strawberry.type
class Query:
me: User = auth.current_user()
@strawberry.type
class Mutation:
login: User = auth.login()
logout = auth.logout()
filters
@strawberry_django.filters.filter(models.Car, lookups=True)
class CarFilter:
id: auto
model : auto
search: str
# question: how should we define the filter function for special fields?
def filter_search(self, queryset):
return queryset.filters(name__contains=self.search)
e.g.
https://github.com/star2000/temp/blob/main/app/models.py
from django.db import models
class A(models.Model):
name = models.CharField(max_length=20)
class B(models.Model):
name = models.CharField(max_length=20)
a = models.ForeignKey(A, models.CASCADE)
https://github.com/star2000/temp/blob/main/app/schema.py
import strawberry
from strawberry_django import ModelResolver
from . import models
class AResolver(ModelResolver):
model = models.A
class BResolver(ModelResolver):
model = models.B
@strawberry.type
class Query(AResolver.query(), BResolver.query()):
pass
@strawberry.type
class Mutation(AResolver.mutation(), BResolver.mutation()):
pass
schema = strawberry.Schema(query=Query, mutation=Mutation)
then send
mutation MyMutation {
createB(data: {name: "test"}) {
id
}
}
get
{
"data": null,
"errors": [
{
"message": "NOT NULL constraint failed: app_b.a_id",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createB"
]
}
]
}
send
mutation MyMutation {
createB(data: {name: "test", a_id: 1}) {
id
}
}
get
{
"data": null,
"errors": [
{
"message": "Field 'a_id' is not defined by type 'CreateB'.",
"locations": [
{
"line": 2,
"column": 32
}
],
"path": null
}
]
}
The expected behavior is to have data.a
or data.a_id
parameter
I've been banging my head against this for a little while and compared to the examples on here, but haven't been able to figure this one out.
My model
from django.db import models
class Contact(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField()
Strawberry types
import strawberry
from strawberry.django import auto
from core import models
@strawberry.django.type(models.Contact)
class Contact:
id: auto
Error
File "Projects/project/project/urls.py", line 19, in <module>
from core.schema import schema
File "Projects/project/core/schema.py", line 3, in <module>
from core.types import Contact
File "Projects/project/core/types.py", line 7, in <module>
class Contact:
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/type.py", line 154, in wrapper
return process_type(cls, model, filters=filters, **kwargs)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/type.py", line 131, in process_type
fields = get_fields(django_type)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/type.py", line 89, in get_fields
field = get_field(django_type, field_name, field_annotation)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/type.py", line 29, in get_field
field = StrawberryDjangoField(
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/fields/field.py", line 60, in __init__
super().__init__(graphql_name=graphql_name, python_name=python_name, **kwargs)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/ordering.py", line 56, in __init__
super().__init__(**kwargs)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/filters.py", line 138, in __init__
super().__init__(**kwargs)
File "Projects/project/env/lib/python3.9/site-packages/strawberry_django/pagination.py", line 24, in __init__
super().__init__(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'type_'
Any ideas where I'm going wrong, or is this a bug?
Thanks
This issue is to discuss whether strawberry-graphql-django should be merged into strawberry, or if it should remain a separate repo.
Hi,
I encounter an error when trying to add pagination for type with custom get_queryset
defined.
[{'message': 'Cannot filter a query once a slice has been taken.', 'locations': [{'line': 2, 'column': 3}], 'path': ['myFavorites']}] <JsonResponse status_code=200, "application/json">
types.py
@strawberry.django.type(models.Favorite, pagination=True)
class Favorite:
id: auto
created_at: auto
def get_queryset(self, queryset, info):
return queryset.filter(user=info.context.request.user)
schema.py
@strawberry.type
class Query:
my_favorites: typing.List[types.Favorite] = strawberry.django.field()
Am I doing something wrong or this is a problem with library which perform slicing (pagination) before calling get_queryset?
With django 3.0 came the choices model field type See here. It would be nice if this integration converted those values into an enum for us so we don't have to declare the same enum twice and the source of truth can remain the model.
E.G.
Declaration in model
class PlayerTypes(models.TextChoices):
FA = "FA", "Free Agent"
PermFA = "PermFA", "Permanent Free Agent"
Signed = "Signed", "Signed"
Inactive = "Inactive", "Inactive"
Example usage?
@strawberry.django.type(models.Player)
class Player:
name: auto
type = strawberry.enum(models.PlayerTypes)
See:
#16
Hey,
in my User model I've got this field:
def user_directory_path(instance, filename):
filename, ext = os.path.splitext(filename)
return os.path.join(
f'profile_pictures/{instance.uuid}/',
f'{uuid.uuid4()}{ext}'
)
...
profile_picture = models.ImageField(
verbose_name=_('profile picture'),
upload_to=user_directory_path,
blank=True,
)
and I guess I'm doing something wrong because I can't define the type of the ImageField.
@types.register
@strawberry_django.type(User, types=types)
class User:
profile_picture: any
profilePicture: any
Whatever I try to define it keeps raising the error. What am I missing?
Also: Does this package support all kind of types like "PhoneNumberField" or "versatileimagefield"? Do I have to define these "special" fields alsways as any?
Thanks!
When defining an enum without the class definition since I am dynamically building it, I get
If I try to use strawberry.enum on the actual field I get
Not a very important issue, but just a small improvement IMO.
@strawberry.enum
class EventLanguage(Enum):
....
@types.register
@strawberry_django.type(
Event,
types=types,
fields=(
...
"language",
),
)
class EventType:
language: EventLanguage = EventLanguage.en.value
Personally I would expect the following in schema:
language: EventLanguage!
But I get
language: String!
removing language
from fields resolves it.
With updating to 0.2.5 all native strawberry.django.fields causing TypeErrors
File "/app/core/urls.py", line 17, in <module>
from api.schema import schema
File "/app/api/schema.py", line 15, in <module>
class Query(
File "/venv/lib/python3.9/site-packages/strawberry/object_type.py", line 158, in type
return wrap(cls)
File "/venv/lib/python3.9/site-packages/strawberry/object_type.py", line 144, in wrap
wrapped = _wrap_dataclass(cls)
File "/venv/lib/python3.9/site-packages/strawberry/object_type.py", line 85, in _wrap_dataclas
return dataclasses.dataclass(cls)
File "/usr/local/lib/python3.9/dataclasses.py", line 1021, in dataclass
return wrap(cls)
File "/usr/local/lib/python3.9/dataclasses.py", line 1013, in wrap
return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen)
File "/usr/local/lib/python3.9/dataclasses.py", line 927, in _process_class
_init_fn(flds,
File "/usr/local/lib/python3.9/dataclasses.py", line 504, in _init_fn
raise TypeError(f'non-default argument {f.name!r} '
TypeError: non-default argument 'tasks' follows default argument
#queries.py
@strawberry.type
class Task:
id: strawberry.ID
uuid: uuid.UUID
name: str
description: Optional[str]
# ...
@strawberry.type
class TaskQueries:
# TASK
tasks: List[Task] = strawberry.django.field()
I have a m2m through tablee that stores extra information to subscription. Here's a basic example:
@strawberry_django.type(Projeect)
class Project:
name: auto
members: List[User]
@strawberry_django.type(User)
class User:
username: auto
# this is the intermediate m2m through table
@strawberry_django.type(Project_Membership)
class Project_Membership:
project: 'Project'
user: 'User'
additional_field_1: auto
additional_field_2: auto
is_admin: auto
can_edit: auto
Now if I want to list all projects of user X with the respective membership information I'd query like this:
{
myProjectMemberships {
canEdit
isAdmin
project {
name
}
# here's the problem: the user field is still exposed which could be used maliciously.
# so the question is if this field can be turned private on conditional cases or
# would I have to create a new type for this scenario?
user {
projectMemberships {
user {
projects {
name
}
}
}
}
}
}
}
To quote @adamcharnock who was/is suggesting the same for graphene-django
When a
DjangoConnectionField
traverses a many-to-many field it would be nice to have the option to expose the fields of any through-table on the edges of the relationship.
In the following scenario:
@strawberry_django.type(Projeect)
class Project:
name: auto
members: List[User]
@strawberry_django.type(User)
class User:
username: auto
# this is the intermediate m2m through table
@strawberry_django.type(Project_Membership)
class Project_Membership:
project: 'Project'
user: 'User'
additional_field_1: auto
additional_field_2: auto
is_admin: auto
can_edit: auto
out of the box this resolves quit well:
query ProjctMembers {
projects {
name
members {
username
}
}
}
however it's not yet possible to access the additional fields of the Project_Membership table. Exposing these fields to the api can be essential in a lot of ways.
With the change of the namespace from strawberry_django
to strawberry.django
it removed VS Codes Intellisense type hinting support for Strawberry.
This was very handy because one could just right click and jump to the definition for types e.g. This is not possible anymore. Now everything is Any
.
It'd be great if permissions can be set for each ModelType.
Inspirational reference package: Graphene-Permissions
For models with a ForeignKey (or a O2O), a pk
arg appears on the generated type, which, to my knowledge doesn't do anything.
Here is the schema used in tests:
type BerryFruit {
id: ID!
name: String!
nameUpper: String!
nameLower: String!
}
type Group {
id: ID!
name: String!
users: [User!]
}
type Query {
user(pk: ID): User!
users: [User!]!
group(pk: ID): Group!
groups: [Group!]!
berries: [BerryFruit!]!
}
type User {
id: ID!
name: String!
group(pk: ID): Group
}
The type User
has a group(pk: ID): Group
. group: Group
should be enough.
change
mutation {
createItem(data: {name: "item1"}) {
id
}
}
to
mutation {
createItems(data: [{name: "item1"}, {name: "item2"}]) {
id
}
}
Hi @la4de! Thanks for making this package. Would you consider moving it to strawberry-graphql organisation?
So we can adopt this as an official extension of strawberry (and maybe also introduce continuous releases).
Let me know what you think! :)
Suppose this is also a feature request to support reverse relationships.
The code below causes: 'Profile' object has no attribute 'all'
Query:
{
users {
id
profile {
id
}
}
}
Schema:
class ProfileResolver(ModelResolver):
model = Profile
fields = (
"id",
)
class UserResolver(ModelResolver):
model = User
fields = (
'id',
'profile'
)
@strawberry.type
class Query(
UserResolver.query(),
):
pass
@strawberry.type
class Mutation(
UserResolver.mutation(),
):
pass
schema = strawberry.Schema(query=Query, mutation=Mutation)
Models:
class Profile(models.Model):
user = models.OneToOneField(
User, # Default Django User model
unique=True,
verbose_name=_("user"),
related_name="profile",
on_delete=models.CASCADE,
)
And for someone running into this in the meantime, the following does work:
class ProfileResolver(ModelResolver):
model = Profile
fields = (
"id",
)
class UserResolver(ModelResolver):
model = User
fields = (
'id',
)
@strawberry.field
def profile(info, root) -> ProfileResolver.output_type:
return root.profile
Maybe the last snippet could be useful in the example.
If you would like me to create a PR for any of this, let me know.
Thanks for awesome project.
I find a problem, when update m2m.
At code "strawberry-graphql-django/tests/mutations/test_relations.py" test,
result = mutation('{ updateGroups(data: { tagsSet: [12] }) { id } }')
=> will set "id==1" and "id==2", NOT "12"
FIX MAY BE
@ strawberry_django.mutations.resolvers.update_m2m_fields
def update_m2m_fields(model, objects, data):
data = utils.get_input_data_m2m(model, data)
if not data:
return
# iterate through objects and update m2m fields
for obj in objects:
for key, actions in data.items():
relation_field = getattr(obj, key)
for key, values in actions.items():
# action is add, set or remove function of relation field
action = getattr(relation_field, key)
# action(*values) #<======= MAY BE BUG
# FIX ------------------------
action(values)
Strawberry-graphql v0.69 introduced many backward incompatible changes. See changelog https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md#0690---2021-07-20.
Task is to update django package to support these changes and revert version pinning (#47).
Hi,
It seems that Django GenericRelation isn't handled correctly. Using @straberry_django.type
decorator with one of my models results in a KeyError: <class 'django.contrib.contenttypes.fields.GenericRelation'>
and it's missing in the field_type_map
in strawberry_django.fields.types
.
My current workaround is to define a field with a custom resolver.
Am I missing something and if not is there any plan to support this type of field?
It's a successful example, in an inelegant way.
Maybe it will inspire you.
use strawberry.file_uploads.Upload
from strawberry.file_uploads import Upload
@strawberry.type
class Mutation:
@strawberry.mutation
def create_goods(self, info: Info, data: t.GoodsInput, picture: Upload) -> t.Goods:
from strawberry_django.utils import get_input_data
instance = m.Goods(picture=picture, **get_input_data(m.Goods, data))
p.owner(info, instance)
return instance
in vue front use createUploadLink
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";
import { createUploadLink } from "apollo-upload-client";
import Vue from "vue";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
const apolloClient = new ApolloClient({
link: createUploadLink({ uri: "/api/" }),
cache: new InMemoryCache(),
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
export default apolloProvider.provide();
then set picture to html File
object
<template>
<el-container>
<el-main>
<el-form>
<el-form-item label="title">
<el-input v-model="title" name="title" />
</el-form-item>
<el-form-item label="describe">
<el-input v-model="describe" name="describe" />
</el-form-item>
<el-form-item label="picture">
<el-upload
action=""
class="avatar-uploader"
:show-file-list="false"
:on-change="set_picture"
:auto-upload="false"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="price">
<el-input
v-model="price"
name="price"
type="number"
step="0.01"
min="0"
/>
</el-form-item>
<el-form-item label="number">
<el-input v-model="number" name="number" type="number" min="1" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">create goods</el-button>
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<script>
import { gql } from "graphql-tag";
export default {
data() {
return {
title: "",
describe: "",
picture: "",
price: "",
number: "",
};
},
methods: {
submit() {
this.$apollo
.mutate({
mutation: gql`
mutation($data: GoodsInput!, $picture: Upload!) {
createGoods(data: $data, picture: $picture) {
id
}
}
`,
variables: {
data: {
title: this.title,
describe: this.describe,
price: parseFloat(this.price),
number: parseInt(this.number),
},
picture: this.picture,
},
})
.then(({ data }) => {
this.$router.push({
name: "detial",
query: { id: data.createGoods.id },
});
})
.catch((error) => {
console.log(error.message);
});
},
set_picture(file) {
this.picture = file.raw;
},
},
computed: {
imageUrl() {
return this.picture ? URL.createObjectURL(this.picture) : "";
},
},
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
NOTE: This is still proposal and subject to change
We are working on new API and first prototype is already available on next branch. New API is more type oriented and it's quite similar with strawberry-graphql pydantic API. The plan is to provide all the functionalities which old API provides today.
Library provides type and input type generation from Django models.
class User(models.Model):
name = models.CharField(max_length=50)
group = models.ForeignKey('Group', null=True, related_name='users', on_delete=models.CASCADE)
tag = models.OneToOneField('Tag', null=True, on_delete=models.CASCADE)
class Group(models.Model):
name = models.CharField(max_length=50)
tags = models.ManyToManyField('Tag', related_name='groups')
class Tag(models.Model):
name = models.CharField(max_length=50)
# types are generated with selected fields from models
@strawberry_django.type(models.User, fields=['id', 'name'])
class User:
# types can be extended
@strawberry.field
def name_upper(root) -> str:
return root.name.upper()
# strawberry_django provides default resolvers for relation fields for your convenience
group: 'Group' = strawberry_django.field()
# forward referencing is supported and field name is configurable :)
user_group: 'Group' = strawberry_django.field(field_name='group')
# all fields can be remapped
my_name: str = strawberry_django.field(field_name='name')
# field can be used as a decorator. Resolver function is guarded with Django's asgiref.sync.sync_to_async
# helper in async context, which means that it is safe to access Django ORM from resolver function
@strawberry_django.field
def user_group_resolver(root, info) -> 'Group':
return model.Group.objects.get(user__id=root.id)
# async resolvers are supported too but then it's user's responsibility to use sync_to_async wrapper
# with Django ORM
@strawberry_django.field
async def user_group_async_resolver(root, info) -> 'Group':
from asgiref.sync import sync_to_async
return sync_to_async(model.Group.objects.get)(user__id=root.id)
@strawberry_django.type(models.Group)
class Group:
pass
@strawberry_django.input(models.User, fields=['group', 'tag'])
class UserInput:
name: str = strawberry_django.field()
@strawberry_django.input(models.Group)
class GroupInput:
pass
@strawberry_django.input(models.Tag, is_update=True)
class TagUpdateInput:
pass
We are discussing about adding this into strawberry core. See strawberry-graphql/strawberry#788
def field_validator(value, info):
if not info.context.request.user.has_permission():
raise Exception('Permission denied')
return value
@strawberry_django.input(models.User)
class UserInput:
field: str = strawberry_django.field(validators=[field_validator])
name: str = strawberry_django.field()
@name.validator
def name_validator(value):
if 'bad' in value:
raise ValueError('name contains word "bad"')
return value.upper()
# we can use validators also from django, wow!
url: str = strawberry_django.field(validators=[url.validator(django.core.validators.URLValidator()])
# option 1
@strawberry.type
class Query:
user = strawberry_django.queries.get(User)
users = strawberry_django.queries.list(User)
# option 2 (queries parameter not implemented yet)
Query = strawberry_django.queries(User, queries=['get', 'list'])
# option 1
@strawberry.type
class Mutation:
create_users = strawberry_django.mutations.create(User, UserInput)
update_users = strawberry_django.mutations.update(User, UserInput)
delete_users = strawberry_django.mutations.delete(User, UserInput)
# option 2 (mutations parameter not implemented yet)
Mutation = strawberry_django.mutations(User, UserInput, mutations=['create', 'update', 'delete'])
schema = strawberry.Schema(query=Query, mutation=Mutation)
Query hooks
Mutation hooks:
@strawberry.type
class Query:
groups = strawberry_django.queries.list(Group)
@groups.queryset
def groups_queryset(info, qs):
user = info.context.request.user
if not user.is_admin():
qs = qs.filter(user__id=user.id)
return qs
def group_post_save(info, instances):
logger.info('saved')
@strawberry.type
class Mutation:
create_groups = strawberry_django.mutations.create(GroupInput, Group, post_save=group_post_save)
@create_group.pre_save
def group_pre_save(info, instances):
instance.name = instance.name.upper()
Type register can be used to extend internal django model field map.
types = strawberry_django.TypeRegister()
@types.register(django.db.models.JsonField):
class MyJsonType:
string: str
integer: int
Model types can be registered in type register. Type register can be passed to type converters and query or mutation generators. Converters and generators use register to resolve field types.
@types.register
@strawberry_django.type(models.User, types=types)
class User:
pass
@types.register
@strawberry_django.type(models.Groups, types=types)
class Group:
pass
Type register can be passed to queries and mutations as well
@strawberry.type
class Query:
user = strawberry_django.queries.get(models.User, types=types)
groups = strawberry_django.queries.list(models.Group, types=types)
@strawberry.type
class Mutation:
create_users = strawberry_django.mutations.create(models.User, types=types)
update_users = strawberry_django.mutations.update(models.User, types=types)
delete_users = strawberry_django.mutations.delete(models.User, types=types)
# or even simpler
Query = strawberry_django.queries(models.User, models.Group, types=types)
Mutation = strawberry_django.mutations(models.User, models.Group, types=types)
Library generates schema with relation field resolvers from given types
query {
user(id: 1) {
...
}
tags {
...
}
}
query {
group(id: 3) {
users {
...
}
}
}
NOTE: Current implementation uses list of strings like this
filters: [ "name__contains='user'", "id__in!=[1,2,3]"]
.
Plan is to start implement graphql types for filters instead of using list of strings.
query {
users(filters: { name__contains: "user" }) {
id
name
tags(filters: { or: { id__gt: 20, not: { id__in: [5, 7, 10] } } }) {
id
}
}
}
query {
users(orderBy: ['-name']) {
...
}
}
TBD
mutation {
createUsers(data: [{ name: "my name", groupId: 5 }]) {
...
}
createUsers(data: [{ name: "user1" }, { name: "user2" }]) {
...
}
}
mutation {
# update basic and foreign key fields
updateUsers(data: { name: "user", groupId: 5}, filters: { ... }) {
...
}
# adding, setting and deleting many to many relations
updateGroups(data: { tagsAdd: [1, 2], tagsSet: [3, 4], tagsRemove: [5] }, filters: { ... }) {
...
}
}
mutation {
# returns list of deleted ids
deleteUsers(filters: { id__in: [1, 5, 9] })
}
More detailed example is available on the next branch:
Tests are also good place to start from:
Feel free to leave any comments or feedback.
Hey,
is there any possibility to add some support for the Django form validation by also returning the field parameters via GraphQL? What I was thinking of is something like this which takes whatever is defined in the model and passes it to the frontend so that I'd be possible to create some auto-validation based on the backend.
User {
"firstname": {
"value": "James",
"validation": {
"type": "String",
"max_length": 30
}
}
}
While authentication is only implemented on a very basic level with login() and logout() for now I think the feedback send to the user could be a little clearer on what's going on. Here are some examples of what's currently being returned:
"""
login with false credentials
"""
mutation Login {
login(username: "[email protected]", password: "wrongpassword") {
username
}
}
"""
username only returns as an empty string.
Here I'm somewhat missing an UserNotFoundError.
How could this be implemented?
"""
{
"data": {
"login": {
"username": ""
}
}
}
"""
login with false credentials and querying for any other
field than username (which is provided as input)
"""
mutation Login {
login(username: "[email protected]", password: "wrongpassword") {
uuid
username
}
}
"""
because no user is found the query is broken.
Instead it should be a UserNotFoundError and
the requested fields set to null or not?
"""
{
"data": null,
"errors": [
{
"message": "'AnonymousUser' object has no attribute 'uuid'",
"locations": [
{
"line": 27,
"column": 5
}
],
"path": [
"login",
"uuid"
]
}
]
}
"""
calling logout without being logged in
"""
mutation Logout {
logout
}
"""
I don't know if this is correct of if it should rather add
a note that the user hasn't been logged in before?
"""
{
"data": {
"logout": true
}
}
I think it'd also be very useful to implement the new ExecutionContext from strawberry to easily check if there have been errors during the execution. Especially for the mentioned login mutations. Is this hard to implement?
What do you think?
As always: TThanks so much for all your work! <3
On https://github.com/la4de/strawberry-graphql-django/blob/main/README.md you had:
class UserResolver(ModelResolver):
model = User
@strawberry.field
def age_in_months(info, root) -> int:
return root.age * 16
I would like to know whether it is
return root.age * 12
instead of:
return root.age * 16
The filter filters: [String!]
couples the query directly to the models' shape, and without schema validation. This can cause many issues, eg:
I think we need to find a better alternative. Maybe we can use Django Filters.
Let's start discussion about schema generation for create and update mutation. We need an API to update relational fields with mutations. Here is one idea how this could be done. I got inspiration from here: https://docs.fauna.com/fauna/current/api/graphql/relations#create
# create object and create, add or set relations
createUser(data: {
# one to one relation
group: {
# set by using primary key
set: { id: 1 }
set: 1 # question, should we accept also id/pk?
# set existing object, filters have to be set so that there is only one matching object (will be implemented later)
set: { username: "friend" }
# create new object and add it to the user (will be implemented later)
create: { name: "best friend" }
}
# many to many relation
groups: {
# add existing objects, can be used together with create
add: [{ id: 1}, { id: 2 }],
add: [1, 2], # question, should we accept also list of id/pk values?
# set, cannot be used together with create or add
set: [{ id: 3 }, { id: 4 }],
# create new objects along with user (will be implemented later)
create: [
{ name: "group 1" }
{ name: "group 2" }
],
}
}) {
groups {
id
name
}
}
# update relation field
updateUser(data: {
# one to one relation
group: {
# set primary key
set: 1
# clear user
set: null
# create new object and add it to the user (will be implemented later)
create: { name: "best friend" }
}
# many to many relation
groups: {
# add new groups
add: [{ id: 1}, { id: 2 }],
# remove groups
remove: [{ id: 3 }, { id: 4 }],
# set the list of objects, cannot be used together with create, add or remove
set: [{ id: 3 }, { id: 4 }],
# clear all objects
set: [],
# create and add objects (will be implemented later)
create: [
{ name: "new group" }
],
}) {
groups {
id
name
}
}
}
Hey there,
I'm on the 'feature/class-api' branch and all my queries and mutations raise an exception:
Traceback (most recent call last):
File "/venv/lib/python3.9/site-packages/graphql/execution/execute.py", line 678, in complete_value_catching_error
completed = self.complete_value(
File "/venv/lib/python3.9/site-packages/graphql/execution/execute.py", line 745, in complete_value
raise result
File "/venv/lib/python3.9/site-packages/graphql/execution/execute.py", line 637, in resolve_field_value_or_error
result = resolve_fn(source, info, **args)
File "/venv/lib/python3.9/site-packages/strawberry/middleware.py", line 29, in resolve
result = next_(root, info, **kwargs)
File "/venv/lib/python3.9/site-packages/strawberry/field.py", line 265, in _resolver
result = self.get_result(_source, info=strawberry_info, kwargs=kwargs)
TypeError: get_result() got multiple values for argument 'kwargs'
Here's a very basic setup to reproduce:
# types.py
import strawberry
import strawberry_django
from strawberry_django import auto, auth
from django.contrib.auth import get_user_model
User = get_user_model()
@strawberry.type
class Test:
name: str = "Hello world"
@strawberry_django.type(User)
class User:
username: auto
first_name: auto
last_name: auto
email: auto
# schema.py
import strawberry
import strawberry_django
from typing import List
from .types import (
User,
Test
)
from .core import auth
@strawberry.type
class Query:
# Trying to see if a non strawberry_django type works -> it doesn't
test: Test = strawberry_django.field()
# USER QUERIES
me: User = auth.current_user()
user: User = strawberry_django.field()
users: List[User] = strawberry_django.field()
@strawberry.type
class Mutation:
login: User = auth.login()
logout = auth.logout()
schema = strawberry.Schema(query=Query, mutation=Mutation)
The only mutation which is not throwing an error is logout
. Everything else returns the exception mentioned before.
What am I missing?
Thanks for your help!
I noticed this project doesn't have a license yet. Without one it's problematic for companies (and actually anyone) to use this project. Can one be added?
I suggest using the MIT license since it's permissive and short and it's what Strawberry uses
>>> pprint(Group.__dict__)
……
'id': <django.db.models.query_utils.DeferredAttribute object at 0x0000028B4FB67490>,
'name': <django.db.models.query_utils.DeferredAttribute object at 0x0000028B4FB59730>,
'natural_key': <function Group.natural_key at 0x0000028B4FB57EE0>,
'objects': <django.db.models.manager.ManagerDescriptor object at 0x0000028B4FB597F0>,
'permissions': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x0000028B4FB670D0>,
'user_set': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x0000028B4FB7B6D0>
……
The error message points to this file
strawberry_django/queries/resolvers.py
Requests can easily blow out to hundreds of queries making it very slow. I'm not sure what the "best way" would be to solve this but I needed to fix it quickly and was able to go from 200 requests in a list of 50 objects to just 1.
Hopefully, there's some better way to have this built-in that would support fragments etc.
@strawberry.django.type(models.InventoryItem, pagination=True)
class InventoryItem:
id: auto
name: str
category: 'InventoryCategory'
supplier: 'InventorySupplier'
def get_queryset(self, queryset, info, **kwargs):
selection_set_node = info.field_nodes[0].selection_set
fields = [selection.name.value for selection in selection_set_node.selections]
related = [field for field in fields if field in ['category', 'supplier']]
return queryset.select_related(*related)
Hello,
How can I set default values for the filter on a query? I'd like to start fruits
query with certain fruits (and certain colors, if possible) and let users to change the filters.
@strawberry.type
class Query:
fruits: List[Fruit] = strawberry.django.field()
Error is thrown from here
https://github.com/django/django/blob/475cffd1d64c690cdad16ede4d5e81985738ceb4/django/db/models/fields/files.py#L40
can you create an slake or something to we have connection to contribute in this project
I want to use Django models with the strawberry federation. But seems like the type methods are conflicting.
Any pointers?
https://docs.djangoproject.com/en/3.2/ref/models/instances/#validating-objects
should validate in mutation
Hi,
django field name is used to determine the field name for the corresponding strawberry type.
This is obviously fine, but in the case of reversed relations, the related_name
would be more appropriate IMO.
Something like that would do the trick:
from django.db.models.fields.reverse_related import ForeignObjectRel
def get_model_fields(cls, model, fields, types, is_input, partial):
[...]
if isinstance(field, ForeignObjectRel)
field_name = field.get_accessor_name()
else:
field_name = field.name
model_fields.append((field_name, field_type, field_value))
I'll draft a PR tomorrow.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.