Skip to content

Commit

Permalink
Merge pull request #80 from tOgg1/develop
Browse files Browse the repository at this point in the history
Version 0.10.0
  • Loading branch information
tOgg1 committed Mar 13, 2022
2 parents 6533a9d + 64c1dde commit 82e3662
Show file tree
Hide file tree
Showing 35 changed files with 919 additions and 406 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Version 0.10.0
* Put `select_for_update` behind a new option `use_select_for_update`, which is enabled by default.
* Rename `only_fields` -> `fields` and `exclude_fields` -> `exclude`. Alias the old names but add deprecation warnings.
* Make the library graphene(-django) 3.x compatible.
* Respect required_fields in Patch and BatchPatch mutations (thanks @mbuvarp).

## Version 0.9.1
* Improve atomicity of patch/update-calls (thanks @keithhackbarth)
* Improve documentation
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,15 @@ class CreateUserMutation(DjangoCreateMutation):
class Mutation(graphene.ObjectType):
create_user = CreateUserMutation.Field()


class Query(graphene.ObjectType):
user = graphene.Field(UserNode, id=graphene.String())

schema = Schema(mutation=Mutation)
def resolve_user(self, info, id):
return User.objects.get(pk=id)


schema = Schema(query=Query, mutation=Mutation)
```

Note that the `UserNode` has to be registered as a field before the mutation is instantiated. This will be configurable in the future.
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
author = 'Tormod Haugland'

# The full version, including alpha/beta/rc tags
release = '0.9.1'
release = '0.10.0'

# -- General configuration ---------------------------------------------------

Expand Down
8 changes: 6 additions & 2 deletions docs/guide/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ Then we can create a create mutation with the following schema
class Meta:
model = User
class Mutation(graphene.ObjectType):
create_user = CreateUserMutation.Field()
class Query(graphene.ObjectType):
user = graphene.Field(UserNode, id=graphene.String())
def resolve_user(self, info, id):
return User.objects.get(pk=id)
schema = Schema(mutation=Mutation)
schema = Schema(query=Query, mutation=Mutation)
Note that the ``UserNode`` has to be registered as a field before the
mutation is instantiated. This will be configurable in the future.
Expand Down
2 changes: 2 additions & 0 deletions docs/ref/models/DjangoPatchMutation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ All meta arguments:
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| one\_to\_one\_extras | Dict | {} | A dict with extra information regarding one to one extras. |
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| use_select_for_update | Boolean | True | If true, the queryset will be altered with ``select_for_update``, locking the database rows in question. Used to ensure data integrity on updates. |
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Example mutation
^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions docs/ref/models/DjangoUpdateMutation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ All meta arguments:
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| one\_to\_one\_extras | Dict | {} | A dict with extra information regarding one to one extras. |
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| use_select_for_update | Boolean | True | If true, the queryset will be altered with ``select_for_update``, locking the database rows in question. Used to ensure data integrity on updates. |
+--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+


.. code::
Expand Down
2 changes: 1 addition & 1 deletion graphene_django_cud/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
Decimal,
)
from graphene.types.json import JSONString
from graphene.utils.str_converters import to_camel_case, to_const
from graphene_django.compat import ArrayField, HStoreField, JSONField, RangeField
from graphene_file_upload.scalars import Upload
from graphql import assert_valid_name, GraphQLError

from graphene_django_cud.types import TimeDelta
from graphene_django_cud.util.string import to_camel_case, to_const


def is_required(field, required=None, is_many_to_many=False):
Expand Down
32 changes: 28 additions & 4 deletions graphene_django_cud/mutations/batch_create.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from collections import OrderedDict
from typing import Iterable

Expand Down Expand Up @@ -29,8 +30,10 @@ def __init_subclass_with_meta__(
model=None,
permissions=None,
login_required=None,
only_fields=(),
exclude_fields=(),
fields=(),
only_fields=(), # Deprecated in favor of `fields`
exclude=(),
exclude_fields=(), # Deprecated in favor of `exclude`
optional_fields=(),
required_fields=(),
auto_context_fields={},
Expand Down Expand Up @@ -73,6 +76,27 @@ def __init_subclass_with_meta__(
# Pluralize
return_field_name = to_snake_case(model.__name__) + "s"

if fields and only_fields:
raise Exception("Cannot set both `fields` and `only_fields` on a mutation")

if exclude and exclude_fields:
raise Exception(
"Cannot set both `exclude` and `exclude_fields` on a mutation"
)

if only_fields:
fields = only_fields
warnings.warn(
"`only_fields` is deprecated in favor of `fields`", DeprecationWarning
)

if exclude_fields:
exclude = exclude_fields
warnings.warn(
"`exclude_fields` is deprecated in favor of `exclude`",
DeprecationWarning,
)

if use_type_name:
input_type_name = use_type_name
InputType = registry.get_converted_field(input_type_name)
Expand All @@ -85,8 +109,8 @@ def __init_subclass_with_meta__(

model_fields = get_input_fields_for_model(
model,
only_fields,
exclude_fields,
fields,
exclude,
tuple(auto_context_fields.keys()) + optional_fields,
required_fields,
many_to_many_extras,
Expand Down
2 changes: 0 additions & 2 deletions graphene_django_cud/mutations/batch_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ def __init_subclass_with_meta__(
model=None,
permissions=None,
login_required=None,
only_fields=(),
exclude_fields=(),
return_field_name=None,
**kwargs,
):
Expand Down
5 changes: 5 additions & 0 deletions graphene_django_cud/mutations/batch_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def __init_subclass_with_meta__(
_meta=None,
model=None,
optional_fields=None,
required_fields=None,
type_name=None,
**kwargs
):
Expand All @@ -28,12 +29,16 @@ def __init_subclass_with_meta__(
if optional_fields is None:
optional_fields = all_field_names

if required_fields is not None:
optional_fields = tuple(set(optional_fields) - set(required_fields))

input_type_name = type_name or f"BatchPatch{model.__name__}Input"

return super().__init_subclass_with_meta__(
_meta=_meta,
model=model,
optional_fields=optional_fields,
required_fields=required_fields,
type_name=input_type_name,
**kwargs
)
32 changes: 28 additions & 4 deletions graphene_django_cud/mutations/batch_update.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from collections import OrderedDict
from typing import Iterable

Expand Down Expand Up @@ -29,8 +30,10 @@ def __init_subclass_with_meta__(
model=None,
permissions=None,
login_required=None,
only_fields=(),
exclude_fields=(),
fields=(),
only_fields=(), # Deprecated in favor of `fields`
exclude=(),
exclude_fields=(), # Deprecated in favor of `exclude`
optional_fields=(),
required_fields=(),
auto_context_fields={},
Expand Down Expand Up @@ -73,6 +76,27 @@ def __init_subclass_with_meta__(
# Pluralize
return_field_name = to_snake_case(model.__name__) + "s"

if fields and only_fields:
raise Exception("Cannot set both `fields` and `only_fields` on a mutation")

if exclude and exclude_fields:
raise Exception(
"Cannot set both `exclude` and `exclude_fields` on a mutation"
)

if only_fields:
fields = only_fields
warnings.warn(
"`only_fields` is deprecated in favor of `fields`", DeprecationWarning
)

if exclude_fields:
exclude = exclude_fields
warnings.warn(
"`exclude_fields` is deprecated in favor of `exclude`",
DeprecationWarning,
)

if use_type_name:
input_type_name = use_type_name
InputType = registry.get_converted_field(input_type_name)
Expand All @@ -85,8 +109,8 @@ def __init_subclass_with_meta__(

model_fields = get_input_fields_for_model(
model,
only_fields,
exclude_fields,
fields,
exclude,
tuple(auto_context_fields.keys()) + optional_fields,
required_fields,
many_to_many_extras,
Expand Down
59 changes: 40 additions & 19 deletions graphene_django_cud/mutations/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import enum
from typing import Iterable, Union

import graphene
from django.db import models
from graphene import Mutation
from graphene.types.mutation import MutationOptions
Expand All @@ -14,7 +16,8 @@
disambiguate_ids,
is_field_many_to_many,
is_field_one_to_one,
is_field_many_to_one, get_model_field_or_none,
is_field_many_to_one,
get_model_field_or_none,
)

meta_registry = get_type_meta_registry()
Expand Down Expand Up @@ -102,23 +105,23 @@ def get_or_create_m2m_objs(cls, field, values, data, operation, info):
info,
{
**input_type_meta.get("auto_context_fields", {}),
**data.get("auto_context_fields", {})
**data.get("auto_context_fields", {}),
},
{
**input_type_meta.get("many_to_many_extras", {}),
**data.get("many_to_many_extras", {})
**data.get("many_to_many_extras", {}),
},
{
**input_type_meta.get("foreign_key_extras", {}),
**data.get("foreign_key_extras", {})
**data.get("foreign_key_extras", {}),
},
{
**input_type_meta.get("many_to_one_extras", {}),
**data.get("many_to_one_extras", {})
**data.get("many_to_one_extras", {}),
},
{
**input_type_meta.get("one_to_one_extras", {}),
**data.get("one_to_one_extras", {})
**data.get("one_to_one_extras", {}),
},
field.related_model,
)
Expand All @@ -136,30 +139,29 @@ def get_or_upsert_m2o_objs(cls, obj, field, values, data, operation, info, Model
field_type = data.get("type", "auto")
for value in values:
if field_type == "ID":
related_obj = field.related_model.objects.get(
pk=cls.resolve_id(value))
related_obj = field.related_model.objects.get(pk=cls.resolve_id(value))
results.append(related_obj)
else:
input_type_meta = meta_registry.get_meta_for_type(field_type)
auto_context_fields = {
**input_type_meta.get("auto_context_fields", {}),
**data.get("auto_context_fields", {})
**data.get("auto_context_fields", {}),
}
many_to_many_extras = {
**input_type_meta.get("many_to_many_extras", {}),
**data.get("many_to_many_extras", {})
**data.get("many_to_many_extras", {}),
}
foreign_key_extras = {
**input_type_meta.get("foreign_key_extras", {}),
**data.get("foreign_key_extras", {})
**data.get("foreign_key_extras", {}),
}
many_to_one_extras = {
**input_type_meta.get("many_to_one_extras", {}),
**data.get("many_to_one_extras", {})
**data.get("many_to_one_extras", {}),
}
one_to_one_extras = {
**input_type_meta.get("one_to_one_extras", {}),
**data.get("one_to_one_extras", {})
**data.get("one_to_one_extras", {}),
}

if field_type == "auto":
Expand Down Expand Up @@ -373,11 +375,17 @@ def create_obj(
new_value = cls.resolve_id(value)
elif field_is_many_to_many:
new_value = cls.resolve_ids(value)
elif isinstance(new_value, enum.Enum):
new_value = new_value.value

if field_is_many_to_many:
many_to_many_to_set[name] = cls.get_all_objs(field.related_model, new_value)
many_to_many_to_set[name] = cls.get_all_objs(
field.related_model, new_value
)
elif field_is_many_to_one:
many_to_one_to_set[name] = cls.get_all_objs(field.related_model, new_value)
many_to_one_to_set[name] = cls.get_all_objs(
field.related_model, new_value
)
else:
model_field_values[name] = new_value

Expand All @@ -391,6 +399,7 @@ def create_obj(

model_field_values[name + "_id"] = obj_id

print(model_field_values)
# Foreign keys are added, we are ready to create our object
obj = Model.objects.create(**model_field_values)

Expand Down Expand Up @@ -647,12 +656,19 @@ def update_obj(
new_value = cls.resolve_id(value)
elif field_is_many_to_many:
new_value = cls.resolve_ids(value)
elif isinstance(new_value, enum.Enum):
new_value = new_value.value

if field_is_many_to_many:
many_to_many_to_set[name] = cls.get_all_objs(field.related_model, new_value)
many_to_many_to_set[name] = cls.get_all_objs(
field.related_model, new_value
)
elif field_is_many_to_one:
many_to_one_to_set[name] = cls.get_all_objs(field.related_model, new_value)
many_to_one_to_set[name] = cls.get_all_objs(
field.related_model, new_value
)
else:
print(obj, name, new_value)
setattr(obj, name, new_value)

# Handle extras fields
Expand Down Expand Up @@ -823,10 +839,15 @@ def resolve_ids(cls, ids):
class DjangoCudBaseOptions(MutationOptions):
model = None

only_fields = None
exclude_fields = None
only_fields = None # Deprecated in favor of `fields`
exclude_fields = None # Deprecated in favor of `exclude`

fields = None
exclude = None

optional_fields = None
required_fields = None

auto_context_fields = None

permissions = None
Expand Down

0 comments on commit 82e3662

Please sign in to comment.