togg1 / graphene-django-cud Goto Github PK
View Code? Open in Web Editor NEWEasy and painless CUD-mutations for graphene-django.
License: MIT License
Easy and painless CUD-mutations for graphene-django.
License: MIT License
They should likely return both the RelayID and the RawID. This should also be overrideable.
When I run tests in django
projects with pytest
and pytest-django
I get some warnings from which at least one is caused by graphene-django-cud
.
.../graphene_django_cud/converter.py:57: RemovedInDjango40Warning: force_text() is deprecated in favor of force_str().
name = to_const(force_text(name))
-- Docs: https://docs.pytest.org/en/latest/warnings.html
============================ SnapshotTest summary ============================
It's not critical cause it's about warnings but a bit annoying cause potential own warnings are kind of hidden. Depending on which django
versions you want to support there needs to be taken some care w.r.t. backward compatibility when fixing the warnings.
TL;DR, Graphene-Django is renaming them, and I assume graphene-django-cud
wants to match it: graphql-python/graphene-django#691
To match Django REST Framework's ModelSerializer
, Graphene-Django is switching the names of only_fields
and exclude_fields
to fields
and exclude
respectively. They've added the new names as aliases already, and are now raising DeprecationWarnings when using the old names, as they plan to remove the old names in v3. I expect that graphene-django-cud
want to follow their lead on this?
Maybe I'm importing incorrectly but this doesn't work for me:
from graphene_django_cud.mutations import DjangoBatchPatchMutation
It seems that the mutatation/__init__.py
file doesn't have the new Batch/Filter Mutations in __all__
.
Above all, many congratulations for such a good job.
Suppose there are two models, a model A and a model B that has a foreign key to model A with and related name = "ab," and also two fields "name" and "value".
So to update a model A and setting as many models B as necessary I specify in UpdateAMutation:
many_to_one_extras = {
"ab": {
"exact": {"type": "auto"}
},
In model B I have the unique type constraint (unique_together = ('name', 'a_id'))
The problem is that when I try to update A and its relations in B in the same mutation, if there is some value in B that already existed with equal ('name', 'a_id'), the constraint throws error.
Please is there a way to do it?
class CreateTableMutation(mutations.DjangoCreateMutation):
class Meta:
model = Table
exclude_fields = ('owner',)
create_column = CreateTableColumnMutation.Field()
@classmethod
def before_save(cls, root, info, input, obj: Table):
obj.owner = User.objects.get(pk=1)
return obj
I've the following code, before_save
doesn't get executed (I copied the example in docs)
this is what I get:
{
"errors": [
{
"message": "NOT NULL constraint failed: query_builder_table.owner_id",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createTable"
]
}
],
"data": {
"createTable": null
}
}
It's supposed to provide the owner object through the before_save, right?
I have a use-case where I need your inputs on how to handle it. Any help is highly appreciated.
Lets say I have these models -
class Dashboard(models.Model):
name = models.CharField(max_length=255)
class Widget(models.Model):
name = models.CharField(max_length=255)
layout = models.CharField(max_length=255)
widget = models.ForeignKey(Dashboard)
class DashboardUpdateMutation(DjangoPatchMutation):
class Meta:
model = Dashboard
login_required = True
class DashboardMutation(graphene.ObjectType):
update_dashboard = DashboardUpdateMutation.Field()
I need to update the Dashboard fields and its widgets through some custom logic. I want to write the patch mutation as follows.
`mutation{
updateDashboard(id: "RGFzaGJvYXJkTm9kZTox", input: {
name: "Main Dashboard",
mycustomfield: "layout json",
}) {
dashboard {
id,
name,
}
}
}`
How should I add mycustomfield
above, so that I can run the logic in its resolver? A code example would help a lot.
Hi Tormod,
I have the following model and mutation defined:
class Beo(BaseModel):
... (some simple fields are here)
organizers = ManyToManyField(
UserProfile, related_name="organized_beos", blank=True,
through="BeoOrganizer", through_fields=("beo", "organizer")
)
history = HistoricalRecords(user_model=UserProfile)
class BeoOrganizer(BaseModel):
beo = ForeignKey(Beo, on_delete=CASCADE)
organizer = ForeignKey(UserProfile, related_name="organized_beo", on_delete=CASCADE)
history = HistoricalRecords(user_model=UserProfile)
class CreateBeo(DjangoCreateMutation):
class Meta:
model = Beo
many_to_many_extras = = {
"organizers": {"exact": {"type": "ID"}},
}
Beo is a model which uses django-simple-history
, a library to keep track of object changes. According to the docs:
"django-simple-history functions by saving history using a post_save signal every time that an object with history is saved."
When I execute the CreateBeo
mutation, the history gets properly recorded for simple fields of the new beo object as expected. However, it does not get recorded for the many-to-many organizers
field even though there is a new entry in the BeoOrganizers
table. I assume this is related to history using the post_save
signal. Is that right? Is this something that could be enabled or is that an inherent limitation?
Many thanks!
One thing that would be nice to have is the possibility to create complex mutations with relations referring to objects that have not yet been created. For instance:
mutation{
createSomething(input: {
_id: "ID1",
field: "Value",
someRelation: [{
anotherField: "value",
someNonObviousRelationField: "ID1"
}]
}){
...
}
Clearly, this issue must include a fair bunch of complex logic to ensure all IDs are resolved before usage.
Hello,
I would like to thank you a lot for creating such a nice library to deal with graphql
I faced an issue with deep nesting when dealing with one to one relation, here is an example :
class Marchendise(models.Model):
type = models.CharField(max_length=100)
class Prevision(models.Model):
marchandise = models.OneToOneField( 'livraison.Marchendise', on_delete=models.CASCADE)
class Rotation(models.Model):
prevision = models.OneToOneField('livraison.Prevision', on_delete=models.CASCADE)
class CreateMarchandiseMutation(DjangoCreateMutation):
class Meta:
model = models.Marchandise
class CreatePrevisionMutation(DjangoCreateMutation):
class Meta:
model = models.Prevision
type_name = "CreatePrevisionType"
one_to_one_extras = {
"marchandise": {"type": "auto"}
}
class RotationWithPrevisionMutation(DjangoCreateMutation):
class Meta:
model = models.Rotation
type_name = "CreateRotationTypeWithPrevision"
one_to_one_extras = {
'prevision': {
"type": "auto"
}
}
when I create a new rotation the query will look like that:
mutation{
create_rotation_with_prevision(input:{
prevision:{
marchendise:"1"
# marchendise:{ type:"container" } ------ What i want to achieve
}
}){
rotation{
id
}
}
The problem is that the type of merchandise is ID instead of nested
thank you in advance
Bug: The ObjectTypeInput, auto generated by a create/update/patch mutation interprets Django Decimal fields as Float fields. Graphene has added support for Decimal fields.
I am required to write my mutation this way:
mutation updateMultipleFieldsProjectl($projectId: ID!, $name: String, $degradation:
Float = 10.00) {
updateProject(id: $projectId, input: {name: $name , degradation: $degradation})
However, in my model, degradation is a django DecimalField.
Currently I am required to hook into before_save
and convert the Float to a Decimal, and define it as a Float in my graphql query.
I have these models:
class Employee(models.Model):
name = models.CharField(max_length=30)
offices = models.ManyToManyField(
"Office",
through="Assignment",
blank=True,
)
class Office(models.Model):
name = models.CharField(max_length=30)
class Assignment(models.Model):
office = models.ForeignKey(
"Office",
on_delete = models.CASCADE,
)
employee = models.ForeignKey(
"Employee",
on_delete = models.CASCADE,
)
primary = models.BooleanField()
How would I go about creating a record in the Assignment model automatically with CreateEmployeeMutation.
My CreateEmployeeMutation looks like this:
class CreateEmployeeMutation(DjangoCreateMutation): # Create
class Meta:
model = Employee
many_to_many_extras = {
"offices": {
"add": {
"type": "CreateAssignmentInput",
}
}
}
The problem is that CreateAssignmentInput requires the ID of the employee which currently being created. If I change "CreateAssignmentInput" to "auto" then it asks for the fields from the Office model as if it's going to add a new office instead of a new Assignment.
Thanks for your great work!
I have the following model and mutation:
class Report(BaseModel):
workspace = ForeignKey(Workspace, on_delete=CASCADE)
name = CharField(max_length=255)
MODULES = (("EVENT", "Event"), ("TASK", "Task"), ("AGENDA", "Agenda"), ("BEO", "Beo"))
module = CharField(max_length=32, choices=MODULES)
author = ForeignKey(UserProfile, on_delete=CASCADE)
config = JSONField()
class UpdateReport(DjangoPatchMutation):
class Meta:
model = Report
type_name = "UpdateReportInput"
module
is NOT optional even though every field should be optional by default in DjangoPatchMutation
according to the documentation:
the examples in the guide do:
mutation {
patchUser(input: {name: "John Doe"}){
user{
id
name
address
}
}
}
but when i do a similar query in my code, with only an input passed i get:
argument \"id\" of type \"ID!\" is required but not provided.
I think it would be very valuable to being able to delete several model instances via one GraphQL mutation call by IDs (or potential other unique fields).
One possible design could combine the fields of DjangoDeleteMutation and DjangoBatchDeleteMutation to something like this in case all IDs have been found
mutation {
deleteModels(id: [1, 2, 3]) {
deletionCount
deletedIds
missedIds
}
}
{
"data": {
"deleteModels": {
"deletionCount": 3,
"deletedIds": [1, 2, 3],
"missedIds": null,
}
}
}
and this in case some Id(s) have not been found
mutation {
deleteModels(id: [1, 2, 3, 4, 5]) {
deletionCount
deletedIds
missedIds
}
}
{
"data": {
"deleteModels": {
"deletionCount": 3,
"deletedIds": [1, 2, 3],
"missedIds": [4, 5],
}
}
}
What do you think?
OneToOneFields should be handled in the same way as ForeignKeys with type auto
.
This is currently not possible, and must use an existing type.
Hi,
I want to exclude a few fields on my related OneToOne model. I have the following code for my create mutation. The nested field created
and modifed
are still available for the creation. Any clues to point me in the right direction is highly appreciated.
class CreateStackMutation(MutationMixin, DjangoCreateMutation):
"""Create dynamic settings"""
class Meta:
model = Stack
exclude_fields = ('modified', 'created', 'email__modified', 'email__created')
one_to_one_extras = {"email": {"type": "auto"}}
hi
I have the following model and mutation:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
contact = models.CharField(max_length=20, null=True, blank=True)
avatar = models.ImageField(upload_to='user/%Y/%m/%d/', default="user.svg")
bio = HTMLField('description', blank=True, null=True, default='Decrivez-vous')
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, created, **kwargs):
instance.profile.save()
class CreateUserMutation(DjangoCreateMutation):
class Meta:
model = User
I got the error when I try to runserver
File "C:\ProgramData\Anaconda3\lib\site-packages\graphene_django_cud\converter.py", line 256, in convert_onetoone_field_to_djangomodel
return List(ID, description=field.help_text, required=is_required(field, required))
AttributeError: 'OneToOneRel' object has no attribute 'help_text'
Hi,
I have a model with a foreign key named kpi
which allude to another model with a PK named key
(with type string). The problem is that whenever I try to insert into the first model with a create mutation and kpi="somekey"
, I get an error regarding that kpi_id=-1
does not exist.
I figured out that it is because of what the disambiguate_id
does to the provided id:
def disambiguate_id(ambiguous_id: Union[int, float, str]):
"""
disambiguate_id takes an id which may either be an integer-parsable
variable, either as a string or a number; or it might be a base64 encoded
global relay value.
The method then attempts to extract from this token the actual id.
:return:
"""
# First see if it is an integer, if so
# it is definitely not a relay global id
final_id = -1
try:
final_id = int(ambiguous_id)
return final_id
except ValueError:
# Try global value
(_, final_id) = from_global_id(ambiguous_id)
finally:
return final_id
this function assumes that if a provided id is not integer-parsable, it is a relay id. However, at least in my case, this is not true. In this case, this function returns -1
which is wrong (I expect it to return my original id, i.e. somekey
).
The problem also exist in the disambiguate_ids
function.
Support manual database selection mentioned here:
https://docs.djangoproject.com/en/3.1/topics/db/multi-db/
Hi @tOgg1, is graphene-django-cud compatible with Relay mutations? https://docs.graphene-python.org/projects/django/en/latest/mutations/#relay
Thanks!
Hello there!
I've been trying to use graphene-django-cud with models where 'id' is not the name of the pk and it crashes. I don't see a way to set that value and 'id__in' seems to be 'hardcoded' in several places. Is there a way of setting custom pks I'm missing?
If not specifying any of the extras, the default handling of such fields are ignored.
Is there a way to extend a mutation with "custom_fields"? We have dynamic custom fields per module, and we don't want to re-code everything on each module that have custom fields. I would like to have a class that I can extends and this class will manage everything. You can find below the code, and the error I'm getting is : Variable "$input" got invalid value In field "customFields": Unknown field. I'm new to python/django/graphene, any help is appreciated! Thanks
Supplier Model
class Supp(base.SoftDeleteMixin, base.TimestampMixin, base.UseCustomField, base.BaseTenantModel):
class Meta:
db_table = 'supp'
verbose_name = 'Supplier'
UseCustomField code
class UseCustomField(TenantModel):
class Meta:
abstract = True
app_label = 'api'
managed = False
field_data_row = models.OneToOneField(
'FieldDataRow',
verbose_name='Custom Field',
on_delete=models.RESTRICT,
null=True, blank=True
)
Supplier Type
class SuppCreateMutation(base.CreateCustomFieldMutation, base.BaseCreateMutation):
class Meta:
login_required = True
model = Supp
one_to_one_extras = {"location": {"type": "auto"}}
Extended Mutation
class CustomField(graphene.InputObjectType):
field = graphene.ID()
value = graphene.String()
class CreateCustomFieldMutation():
class Meta:
abstract = True
custom_fields = {
"customFields": graphene.List(CustomField)
}
@classmethod
def before_save(cls, root, info, input, obj: Supp):
if input.get("customFields"):
field_data_row = FieldDataRow.objects.create()
obj.field_data_row = field_data_row
for field in input.get("customFields"):
FieldDataValue.objects.create(field_data_row=field_data_row, field=Field(pk=field.field), value=field.value)
return obj
We have some duplicated logic here which is unnecessary.
python version 3.10.3
django version 4.0.3
graphene-django from main branch latest commit by date of posting(f6ec0689c18929344c79ae363d2e3d5628fa4a2d)
graphene_django_cud version 0.10.0
I was trying to create a DjangoCreateMutation(actually it fails for any kind of mutation) with this model
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Staff(models.Model):
class StaffStatus(models.TextChoices):
WORKING = 'working', 'Working'
SUSPENDED = 'suspended', 'Suspended'
FIRED = 'fired', 'Fired'
first_name = models.CharField(max_length=64)
middle_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)
phone = models.CharField(max_length=32)
email = models.CharField(max_length=256)
position = models.CharField(max_length=64)
address = models.CharField(max_length=512)
status = models.CharField(max_length=16, choices=StaffStatus.choices, default=StaffStatus.WORKING)
is_admin = models.BooleanField(default=False)
user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, related_name='staff')
def __str__(self):
return f'{self.last_name} {self.first_name}({self.id})'
I suppose most of the fields can be skipped except the one with choices which was created via class _(models.TextChoices) or its analogues in it.
And when I tried to run app it fails with message
File "lib\site-packages\graphene_django_cud\converter.py", line 116, in convert_django_field_with_choices
from_registry.kwargs['description'] = field.help_text
AttributeError: 'BlankValueField' object has no attribute 'kwargs'. Did you mean: 'args'?
Currently, graphene-django-cud is very persistent on returning relay IDs. We'd probably want this to be configurable, as other parts of the library are agnostic in this respect.
The required_fields
meta option does not work for DjangoPatchMutation
. While I get that generally in patch mutations all fields should be optional, I still think fields should be required if explicitly set as such.
When creating a simple model with a choice charfield:
# Model
BAR_CHOICES = [
('OPTION1', 'Option 1'),
('OPTION2', 'Option 2'),
('OPTION3', 'Option 3')
]
class Foo(models.Model):
bar = models.CharField(max_length=32, choices=BAR_CHOICES, null=True, blank=True)
# Type
from graphene_django import DjangoObjectType as DOT
class FooType(DOT):
class Meta:
model = Foo
filter_fields: Dict[str, str] = {}
# Mutation
class CreateFoo(DjangoCreateMutation):
class Meta:
model = Foo
This results in two identical enum types being generated with different names, FooBar
and FooBarInput
. Is this intended behavior, or a valid issue?
I have a model called Beo
with a many-to-many field called labels
like this:
class Beo(BaseModel):
...
labels = ManyToManyField(BeoLabel, blank=True)
I also have a create mutation for labels with the default input type:
In the create mutation, I would like to specify that it is possible to create labels directly while creating the Beo instance:
class CreateBeo(DjangoCreateMutation):
class Meta:
model = Beo
many_to_many_extras = {
"labels": {"exact": {"type": "CreateBeoLabelInput"}},
In the resulting mutation, however, I only see the [ID]
as the input type for labels while expecting something like [CreateBeoLabelInput]
:
This is for better typing and method signature reasons.
Would be nice having a field to restrict the available queryset for update and patch mutations based on context values. For example, say you have the field organization
in the context. You could do something like this:
class PatchUserMutation(DjangoPatchMutation):
class Meta:
model = User
auto_context_queryset_filter = {
"organization": "organization"
}
A user would then only be able to patch users in the same organization as them.
There is likely a use case for having a DjangoUpsertMutation.
I have found another somewhat related issue for the following model and mutation:
class Beo(BaseModel):
...
organizers = ManyToManyField(
UserProfile, related_name="organized_beos", blank=True,
through="BeoOrganizer", through_fields=("beo", "organizer")
)
class BeoOrganizer(BaseModel):
beo = ForeignKey(Beo, on_delete=CASCADE)
organizer = ForeignKey(UserProfile, related_name="organized_beo", on_delete=CASCADE)
class UpdateBeo(DjangoPatchMutation):
class Meta:
model = Beo
many_to_many_extras = = {
"organizers": {"exact": {"type": "ID"}},
}
When using exact
in DjangoPatchMutation, I would expect that the following mutation would clear the organizers list. In reality, nothing changes:
Hello,
Currently login_required
is ignored in all mutations, I think the problematic line is _meta.login_required = _meta.login_required or (
instead of _meta.login_required = login_required or (
.
Take a look please, thanks!
In light of #28, we need to improve primary key handling, as the type should be "ID" by default. However, we need to severely harden how primary keys are handled throughout the system, as some primary keys may be non-integer, non-auto-incremented fields, which will break.
Also, #28 would break setups where you'd want a non-required primary key field.
Model:
class Scholarship(models.Model):
name = models.CharField(max_length=30)
class Student(models.Model):
name = models.CharField(max_length=30)
rollno = models.CharField(max_length=30)
scholarships = models.ArrayReferenceField(Scholarship, on_delete=models.CASCADE)
class Dashboard(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='dashboards')
class Meta:
ordering = ['-id']
unique_together = ['name', 'owner']
The validation error gets hit properly, but the message field is empty for this error.
{"errors":[{"message":"","locations":[{"line":2,"column":21}],"path":["createDashboard"]}],"data":{"createDashboard":null}}
I am getting an error when using a custom field type to upload a file with the graphene-file-upload library.
This is the model
class Candidate(models.Model):
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
resume = models.FileField()
This code incorporate graphene-django-cud Create Mutation with the Upload class from graphene-file-upload
class CreateCandidateMutation(DjangoCreateMutation):
class Meta:
model = Candidate
field_types = {
"resume": Upload(required=True)
}
@classmethod
def handle_resume(cls, value, *args, **kwargs):
return value[0]
The server then response with this error: Object of type InMemoryUploadedFile is not JSON serializable
This code works when I write my own mutation without this library:
class CandidateCreateInput(graphene.InputObjectType):
first_name = graphene.String(required=False)
last_name = graphene.String(required=False)
resume = Upload(required=False)
class CandidateCreateMutation(graphene.Mutation):
class Arguments:
input = CandidateCreateInput(required=True)
candidate = graphene.Field(CandidateNode)
def mutate(self, info, input=None):
candidate = Candidate.objects.create(
resume = input.resume[0]
)
return CandidateCreateMutation(candidate=candidate)
When I look in the GraphiQL docs, the resume field shows as an Upload scalar type when I use the django-graphene-cud mutation, but shows as a String type with my vanilla graphene mutation.
I have the following field in my Beo model:
class Beo(BaseModel):
...
STATUSES = (("UNLOCKED", "Unlocked"), ("LOCKED", "Locked"), ("SIGNED", "Signed"))
status = CharField(max_length=16, choices=STATUSES, default="UNLOCKED")
When using DjangoCreateMutation, I get an enumeration for this field as expected:
When I call the mutation without including the status field, I get the following error message even though I specify a default in the Beo model:
In field \"status\": Expected \"BeoStatus!\", found null.",
When I tried to create a mutation using DjangoCreateMutation
I am getting
NameError: name 'DjangoCreateMutation' is not defined
Plz help me how to resolve this
Hi I have these models
class package(models.Model):
items = models.ManyToManyField('product.item', verbose_name=_("Items"), related_name="package_items")
class item(models.Model):
products = models.ManyToManyField('product.product', verbose_name=_("Products"), null=True, blank=True)
class product(models.Model):
options = models.ManyToManyField("product.option", verbose_name=_("Product options"), null=True, blank=True)
and this is my mutation for package
and item
class create_package(DjangoCreateMutation):
class Meta:
model = package
many_to_many_extras = {
'items': {
'add':{'type': 'CreateitemInput'}
}
}
class create_item(DjangoCreateMutation):
class Meta:
model = item
many_to_many_extras = {
'products': {
'add':{'type': 'CreateproductInput'}
}
}
class create_product(DjangoCreateMutation):
class Meta:
model = product
many_to_many_extras = {
'options': {
'add':{'type': 'CreateoptionInput'}
}
}
And when I run a mutation like this:
mutation{
createPackage(
input:{
name:"random"
description:"boring"
sideBarText:"sample"
items:[
1
]
itemsAdd:[
{
name:"bla bla bla"
displayName:"kdjhb"
products:[
"cHJvZHVjdF90eXBlOjE="
]
productsAdd:[
{
name:"product bla"
description:"double trouble"
price:"99.99"
}
]
}
]
}
){
package{
name
items{
edges{
node{
name
products{
edges{
node{
name
price
}
}
}
}
}
}
}
}
}
I only get the added product returned, why isn't the other product being added?
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.