Giter Club home page Giter Club logo

strawberry-django's People

Contributors

augustebaum avatar bellini666 avatar benhowes avatar benjaminderei avatar devkral avatar fabien-michel avatar fireteam99 avatar flickersoul avatar frleb avatar g-as avatar illia-v avatar joewhoward avatar joeydebreuk avatar kwongtn avatar la4de avatar maniacmaxo avatar mapiarz avatar miyashiiii avatar noelleleigh avatar nrbnlulu avatar patrick91 avatar pre-commit-ci[bot] avatar sdobbelaere avatar sjdemartini avatar star2000 avatar thepapermen avatar tokr-bit avatar vecchp avatar whardeman avatar zvyn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

strawberry-django's Issues

Reduce queries with prefetching

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)

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

When foreign key points to settings.AUTH_USER_MODEL

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.

Support django Choices for graphql enums

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)

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

How to use django model types with federation?

I want to use Django models with the strawberry federation. But seems like the type methods are conflicting.

Any pointers?

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Unwanted argument in model Types for O2O and ForeignKeys

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.

strawberry_django.type fields arg overwrites class fields

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.

Feature request: Support for Django form validation

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
    }
  }
}

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Proposal for class-oriented API

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:

  • 9.4.2021 - initial draft
  • 10.4.2021 - API adjustments
  • 4.5.2021 - Add couple of new examples and update existing ones
  • 12.5.2021 - Add link to docs and update filtering api

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)

Make m2m through table fields available

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.

Software license

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

Import error in example from README.md

When trying to create a type similar to the example in README.md, an error occurs:

imagen

This is the example in README.md:

imagen

The error can be solved by changing import strawberry to import strawberry.django, so I think README.md should be changed:

imagen

When creating mutation, there is no foreign key ID field in the parameter list

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

Upload file or image

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>

Feature request: (edge/node) permissions

It'd be great if permissions can be set for each ModelType.

Inspirational reference package: Graphene-Permissions

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Field name of reversed relations

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.

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

contributing

can you create an slake or something to we have connection to contribute in this project

Cannot filter a query once a slice has been taken - pagination with custom get_queryset

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?

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Infinite recursion prevention: Make fields private when coming from m2m table

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
            }
          }
        }
      }
    }
  }
}

Authentication: misleading returns and errors

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

support: ideas to using/migrating this with Tortoise ORM?

Is there any way to abstract this plugin or it needs to be rewritten for support for Tortoise ORM?

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Setting Default Values for Filters

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()

TypeError: No type defined for 'ImageField'

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!

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Feature request: A better way to filter

The filter filters: [String!] couples the query directly to the models' shape, and without schema validation. This can cause many issues, eg:

  • To use filters you need to know the shape of the Django models.
  • If there are changes in the Django model, the existing filters might break, without being able to check this with schema validation

I think we need to find a better alternative. Maybe we can use Django Filters.

Proposal for the new API

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.

Changelog

  • 17.3.2021 - first draft
  • 19.3.2021 - new content
  • 20.3.2021 - implementation status update
  • 23.3.2021 - query, mutation and type register updates
  • 24.3.2021 - type definition updates, relational_field removed, create_batch renamed to create

Type definitions

Library provides type and input type generation from Django models.

Example 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)

Defining types (implemented)

# 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

Defining input types (implemented)

@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

Validators and pre/post processors (not implemented)

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()])

Defining queries (implemented)

# 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'])

Defining mutations (implemented)

# 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 and mutation hooks (implemented)

Query hooks

  • pre/post query (not implemented yet)
  • queryset
  • etc

Mutation hooks:

  • pre/post save
  • pre/update update (not implemented yet)
  • pre/post delete (not implemented yet)
  • etc
@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 registers (implemented)

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)

Queries

Library generates schema with relation field resolvers from given types

Basic queries (implemented)

query {
  user(id: 1) {
  ...
  }
  tags {
  ...
  }
}

Reverse relations (implemented)

query {
  group(id: 3) {
    users {
      ...
    }
  }
}

Filtering (partially implemented)

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
    }
  }
}

Ordering (implemented)

query {
  users(orderBy: ['-name']) {
    ...
  }
}

Pagination (not implemented)

TBD

Mutations

Creating objects (implemented)

mutation {
  createUsers(data: [{ name: "my name", groupId: 5 }]) {
    ...
  }
  createUsers(data: [{ name: "user1" }, { name: "user2" }]) {
    ...
  }
}

Updating objects (implemented)

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: { ... }) {
    ...
  }
}

Deleting objects (implemented)

mutation {
  # returns list of deleted ids
  deleteUsers(filters: { id__in: [1, 5, 9] })
}

Prorotype and example project

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.

Can't resolve attribute that end with '_set'

image

>>> 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

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Strawberry.enum does not return a valid class type

When defining an enum without the class definition since I am dynamically building it, I get

image

If I try to use strawberry.enum on the actual field I get

image

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Getting "got an unexpected keyword argument 'type_'" with basic example

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

Relational fields in create and update mutations

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

# 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

  # 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
    }
  }
}

Namespace "strawberry.django" removed type hinting support in VS Code

With the change of the namespace from strawberry_django to strawberry.django it removed VS Codes Intellisense type hinting support for Strawberry.

Before:
grafik

After:
grafik

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.

Before:
grafik

After:
grafik

Partial update fields do not support many-to-many relationships.

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'

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

update_m2m_fields Problem.

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)

Should there be a default user register mutation?

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.

Batch create mutation

change

mutation {
  createItem(data: {name: "item1"}) {
    id
  }
}

to

mutation {
  createItems(data: [{name: "item1"}, {name: "item2"}]) {
    id
  }
}

[Bug] 0.2.5 "TypeError: non-default argument 'users' follows default argument"

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()

"'ModelClass' object has no attribute 'all'"

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.

GenericRelation not supported?

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?

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

TypeError: get_result() got multiple values for argument 'kwargs'

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!

auto type hint throwing error when using __future__

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'>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.