Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: auto_context_queryset_filter #50

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions graphene_django_cud/mutations/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ class DjangoCudBaseOptions(MutationOptions):
optional_fields = None
required_fields = None
auto_context_fields = None
auto_context_queryset_filter = None

permissions = None
login_required = None
Expand Down
20 changes: 19 additions & 1 deletion graphene_django_cud/mutations/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import graphene
from django.db import transaction
from django.db.models import Q
from graphene import InputObjectType
from graphene.types.utils import yank_fields_from_attrs
from graphene.utils.str_converters import to_snake_case
Expand Down Expand Up @@ -34,6 +35,7 @@ def __init_subclass_with_meta__(
optional_fields=(),
required_fields=(),
auto_context_fields=None,
auto_context_queryset_filter=None,
return_field_name=None,
many_to_many_extras=None,
foreign_key_extras=None,
Expand All @@ -53,6 +55,9 @@ def __init_subclass_with_meta__(
if auto_context_fields is None:
auto_context_fields = {}

if auto_context_queryset_filter is None:
auto_context_queryset_filter = {}

if many_to_one_extras is None:
many_to_one_extras = {}

Expand Down Expand Up @@ -97,6 +102,7 @@ def __init_subclass_with_meta__(
input_type_name,
{
"auto_context_fields": auto_context_fields or {},
"auto_context_queryset_filter": auto_context_queryset_filter or {},
"optional_fields": optional_fields,
"required_fields": required_fields,
"many_to_many_extras": many_to_many_extras,
Expand Down Expand Up @@ -126,6 +132,7 @@ def __init_subclass_with_meta__(
_meta.optional_fields = optional_fields
_meta.required_fields = required_fields
_meta.auto_context_fields = auto_context_fields
_meta.auto_context_queryset_filter = auto_context_queryset_filter
_meta.many_to_many_extras = many_to_many_extras
_meta.many_to_one_extras = many_to_one_extras
_meta.foreign_key_extras = foreign_key_extras
Expand Down Expand Up @@ -178,10 +185,21 @@ def mutate(cls, root, info, input, id):
if cls._meta.login_required and not info.context.user.is_authenticated:
raise GraphQLError("Must be logged in to access this mutation.")


id = disambiguate_id(id)
Model = cls._meta.model
queryset = cls.get_queryset(root, info, input, id)

extend_query = False
query_extension = Q()
auto_context_queryset_filter = cls._meta.auto_context_queryset_filter or {}
for field_name, context_name in auto_context_queryset_filter.items():
if hasattr(info.context, context_name):
extend_query = True
query_extension &= Q(**{field_name: getattr(info.context, context_name)})

if extend_query:
queryset = queryset.filter(query_extension)
Comment on lines +192 to +201
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tOgg1 Should this thing perhaps be placed somewhere else?


obj = queryset.get(pk=id)
auto_context_fields = cls._meta.auto_context_fields or {}

Expand Down
98 changes: 98 additions & 0 deletions graphene_django_cud/tests/test_update_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1610,3 +1610,101 @@ class Mutations(graphene.ObjectType):

dog.refresh_from_db()
self.assertEqual(1, dog.bark_count)


class TestUpdateMutationAutoContextQuerysetFilter(TestCase):
def test_auto_context_queryset_filter__correct_owner__does_update(self):
# This registers the UserNode type
# noinspection PyUnresolvedReferences
from .schema import UserNode

class UpdateDogMutation(DjangoUpdateMutation):
class Meta:
model = Dog
auto_context_queryset_filter = {"owner": "user"}

class Mutations(graphene.ObjectType):
update_dog = UpdateDogMutation.Field()

user = UserFactory.create()
dog = DogFactory.create(owner=user)

schema = Schema(mutation=Mutations)
mutation = """
mutation UpdateDog(
$id: ID!,
$input: UpdateDogInput!
){
updateDog(id: $id, input: $input){
dog{
id
}
}
}
"""

result = schema.execute(
mutation,
variables={
"id": to_global_id("DogNode", dog.id),
"input": {
"name": "Lassie",
"tag": "tag",
"breed": "LABRADOR", # Yes I know Lassie isn't a labrador
"owner": to_global_id("UserNode", user.id)
}
},
context=Dict(user=user)
)
self.assertIsNone(result.errors)

dog.refresh_from_db()
self.assertEqual("Lassie", dog.name)

def test_auto_context_queryset_filter__wrong_owner__does_not_update(self):
# This registers the UserNode type
# noinspection PyUnresolvedReferences
from .schema import UserNode

class UpdateDogMutation(DjangoUpdateMutation):
class Meta:
model = Dog
auto_context_queryset_filter = {"owner": "user"}

class Mutations(graphene.ObjectType):
update_dog = UpdateDogMutation.Field()

user = UserFactory.create()
dog = DogFactory.create(name="Lyng")

schema = Schema(mutation=Mutations)
mutation = """
mutation UpdateDog(
$id: ID!,
$input: UpdateDogInput!
){
updateDog(id: $id, input: $input){
dog{
id
}
}
}
"""

result = schema.execute(
mutation,
variables={
"id": to_global_id("DogNode", dog.id),
"input": {
"name": "Lassie",
"tag": "tag",
"breed": "LABRADOR", # Yes I know Lassie isn't a labrador
"owner": to_global_id("UserNode", user.id)
}
},
context=Dict(user=user)
)
self.assertIsNotNone(result.errors)

dog.refresh_from_db()
self.assertEqual("Lyng", dog.name)