Skip to content

Commit

Permalink
Change metadata mutations to use token for order and checkout as iden…
Browse files Browse the repository at this point in the history
…tifier (saleor#8426)

* Change metadata mutations to use token for order and checkout as identifier

* Update changelog

* Add warning for changing order meta by order id
  • Loading branch information
IKarbowiak authored Nov 8, 2021
1 parent 55b3625 commit 495501a
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ All notable, unreleased changes to this project will be documented in this file.
- drop `channel` field from filters and sorters
- Drop top-level `checkoutLine` query from the schema with related resolver, use `checkout` query instead - #7623 by @dexon44
- Make SKU an optional field on `ProductVariant` - #7633 by @rafalp
- Change metadata mutations to use token for order and checkout as identifier - #8426 by @IKarbowiak
- After changes, using the order `id` for changing order metadata is deprecated

### Other

Expand Down
129 changes: 85 additions & 44 deletions saleor/graphql/meta/mutations.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import warnings
from typing import List

import graphene
from django.core.exceptions import ValidationError
from graphql.error.base import GraphQLError

from ...checkout import models as checkout_models
from ...core import models
from ...core.error_codes import MetadataErrorCode
from ...core.exceptions import PermissionDenied
from ...discount import models as discount_models
from ...menu import models as menu_models
from ...order import models as order_models
from ...product import models as product_models
from ...shipping import models as shipping_models
from ..channel import ChannelContext
Expand Down Expand Up @@ -52,7 +55,32 @@ def __init_subclass_with_meta__(
def get_instance(cls, info, **data):
object_id = data.get("id")
qs = data.get("qs", None)
return cls.get_node_or_error(info, object_id, qs=qs)

try:
type_name, _ = from_global_id_or_error(object_id)
if type_name == "Order":
warnings.warn("DEPRECATED. Use token for changing order metadata.")
# ShippingMethod type isn't model-based class
if type_name == "ShippingMethod":
qs = shipping_models.ShippingMethod.objects
return cls.get_node_or_error(info, object_id, qs=qs)
except GraphQLError as e:
if instance := cls.get_instance_by_token(object_id, qs):
return instance
raise ValidationError({"id": ValidationError(str(e), code="graphql_error")})

@classmethod
def get_instance_by_token(cls, object_id, qs):
if not qs:
if order := order_models.Order.objects.filter(token=object_id).first():
return order
if checkout := checkout_models.Checkout.objects.filter(
token=object_id
).first():
return checkout
return None
if qs and "token" in [field.name for field in qs.model._meta.get_fields()]:
return qs.filter(token=object_id).first()

@classmethod
def validate_model_is_model_with_metadata(cls, model, object_id):
Expand All @@ -79,16 +107,10 @@ def validate_metadata_keys(cls, metadata_list: List[dict]):
)

@classmethod
def get_model_for_type_name(cls, info, type_name):
graphene_type = info.schema.get_type(type_name).graphene_type
return graphene_type._meta.model

@classmethod
def get_permissions(cls, info, **data):
object_id = data.get("id")
if not object_id:
def get_permissions(cls, info, type_name, object_pk, **data):
if object_pk is None:
return []
type_name, object_pk = from_global_id_or_error(object_id)
object_id = data.get("id")
model = cls.get_model_for_type_name(info, type_name)
cls.validate_model_is_model_with_metadata(model, object_id)
permission = cls._meta.permission_map.get(type_name)
Expand All @@ -100,10 +122,18 @@ def get_permissions(cls, info, **data):
"and PUBLIC_META_PERMISSION_MAP"
)

@classmethod
def get_model_for_type_name(cls, info, type_name):
if type_name == "ShippingMethod":
return shipping_models.ShippingMethod
graphene_type = info.schema.get_type(type_name).graphene_type
return graphene_type._meta.model

@classmethod
def mutate(cls, root, info, **data):
type_name, object_pk = cls.get_object_type_name_and_pk(data)
try:
permissions = cls.get_permissions(info, **data)
permissions = cls.get_permissions(info, type_name, object_pk, **data)
except GraphQLError as e:
error = ValidationError(
{"id": ValidationError(str(e), code="graphql_error")}
Expand All @@ -116,15 +146,36 @@ def mutate(cls, root, info, **data):
try:
result = super().mutate(root, info, **data)
if not result.errors:
cls.perform_model_extra_actions(root, info, **data)
cls.perform_model_extra_actions(root, info, type_name, **data)
except ValidationError as e:
return cls.handle_errors(e)
return result

@classmethod
def perform_model_extra_actions(cls, root, info, **data):
def get_object_type_name_and_pk(cls, data):
object_id = data.get("id")
if not object_id:
return None, None
try:
return from_global_id_or_error(object_id)
except GraphQLError:
if order := order_models.Order.objects.filter(token=object_id).first():
return "Order", order.pk
if checkout := checkout_models.Checkout.objects.filter(
token=object_id
).first():
return "Checkout", checkout.pk
raise ValidationError(
{
"id": ValidationError(
"Couldn't resolve to a node.", code="graphql_error"
)
}
)

@classmethod
def perform_model_extra_actions(cls, root, info, type_name, **data):
"""Run extra metadata method based on mutating model."""
type_name, _ = from_global_id_or_error(data["id"])
if MODEL_EXTRA_METHODS.get(type_name):
prefetch_method = MODEL_EXTRA_PREFETCH.get(type_name)
if prefetch_method:
Expand Down Expand Up @@ -162,37 +213,18 @@ class MetadataInput(graphene.InputObjectType):
value = graphene.String(required=True, description="Value of a metadata item.")


class ShippingMethodMetadataMixin:
@classmethod
def get_instance(cls, info, **data):
object_id = data.get("id")
qs = data.get("qs", None)

type_name, _ = from_global_id_or_error(object_id)
# ShippingMethod type isn't model-based class
if type_name == "ShippingMethod":
qs = shipping_models.ShippingMethod.objects

return cls.get_node_or_error(info, object_id, qs=qs)

@classmethod
def get_model_for_type_name(cls, info, type_name):
# ShippingMethod type isn't model-based class
if type_name == "ShippingMethod":
return shipping_models.ShippingMethod
graphene_type = info.schema.get_type(type_name).graphene_type
return graphene_type._meta.model


class UpdateMetadata(ShippingMethodMetadataMixin, BaseMetadataMutation):
class UpdateMetadata(BaseMetadataMutation):
class Meta:
description = "Updates metadata of an object."
permission_map = PUBLIC_META_PERMISSION_MAP
error_type_class = MetadataError
error_type_field = "metadata_errors"

class Arguments:
id = graphene.ID(description="ID of an object to update.", required=True)
id = graphene.ID(
description="ID or token (for Order and Checkout) of an object to update.",
required=True,
)
input = graphene.List(
graphene.NonNull(MetadataInput),
description="Fields required to update the object's metadata.",
Expand All @@ -211,15 +243,18 @@ def perform_mutation(cls, root, info, **data):
return cls.success_response(instance)


class DeleteMetadata(ShippingMethodMetadataMixin, BaseMetadataMutation):
class DeleteMetadata(BaseMetadataMutation):
class Meta:
description = "Delete metadata of an object."
permission_map = PUBLIC_META_PERMISSION_MAP
error_type_class = MetadataError
error_type_field = "metadata_errors"

class Arguments:
id = graphene.ID(description="ID of an object to update.", required=True)
id = graphene.ID(
description="ID or token (for Order and Checkout) of an object to update.",
required=True,
)
keys = graphene.List(
graphene.NonNull(graphene.String),
description="Metadata keys to delete.",
Expand All @@ -237,15 +272,18 @@ def perform_mutation(cls, root, info, **data):
return cls.success_response(instance)


class UpdatePrivateMetadata(ShippingMethodMetadataMixin, BaseMetadataMutation):
class UpdatePrivateMetadata(BaseMetadataMutation):
class Meta:
description = "Updates private metadata of an object."
permission_map = PRIVATE_META_PERMISSION_MAP
error_type_class = MetadataError
error_type_field = "metadata_errors"

class Arguments:
id = graphene.ID(description="ID of an object to update.", required=True)
id = graphene.ID(
description="ID or token (for Order and Checkout) of an object to update.",
required=True,
)
input = graphene.List(
graphene.NonNull(MetadataInput),
description=("Fields required to update the object's metadata."),
Expand All @@ -264,15 +302,18 @@ def perform_mutation(cls, root, info, **data):
return cls.success_response(instance)


class DeletePrivateMetadata(ShippingMethodMetadataMixin, BaseMetadataMutation):
class DeletePrivateMetadata(BaseMetadataMutation):
class Meta:
description = "Delete object's private metadata."
permission_map = PRIVATE_META_PERMISSION_MAP
error_type_class = MetadataError
error_type_field = "metadata_errors"

class Arguments:
id = graphene.ID(description="ID of an object to update.", required=True)
id = graphene.ID(
description="ID or token (for Order and Checkout) of an object to update.",
required=True,
)
keys = graphene.List(
graphene.NonNull(graphene.String),
description="Metadata keys to delete.",
Expand Down
Loading

0 comments on commit 495501a

Please sign in to comment.