class Query(graphene.ObjectType): plan = gql_optimizer.field( graphene.Field(PlanNode, id=graphene.ID(required=True))) all_plans = graphene.List(PlanNode) action = graphene.Field(ActionNode, id=graphene.ID(), identifier=graphene.ID(), plan=graphene.ID()) indicator = graphene.Field(IndicatorNode, id=graphene.ID(), identifier=graphene.ID(), plan=graphene.ID()) person = graphene.Field(PersonNode, id=graphene.ID(required=True)) static_page = graphene.Field(StaticPageNode, plan=graphene.ID(required=True), slug=graphene.ID(required=True)) plan_actions = graphene.List(ActionNode, plan=graphene.ID(required=True), first=graphene.Int(), order_by=graphene.String()) plan_categories = graphene.List(CategoryNode, plan=graphene.ID(required=True), category_type=graphene.ID()) organizations = graphene.List( OrganizationNode, plan=graphene.ID(), with_ancestors=graphene.Boolean(default_value=False)) plan_indicators = graphene.List( IndicatorNode, plan=graphene.ID(required=True), first=graphene.Int(), order_by=graphene.String(), has_data=graphene.Boolean(), has_goals=graphene.Boolean(), ) def resolve_plan(self, info, **kwargs): qs = Plan.objects.all() try: plan = gql_optimizer.query(qs, info).get(identifier=kwargs['id']) except Plan.DoesNotExist: return None return plan def resolve_all_plans(self, info): return Plan.objects.all() def resolve_plan_actions(self, info, plan, first=None, order_by=None, **kwargs): qs = Action.objects.all() qs = qs.filter(plan__identifier=plan) qs = order_queryset(qs, ActionNode, order_by) if first is not None: qs = qs[0:first] return gql_optimizer.query(qs, info) def resolve_categories(self, info, category_type): qs = self.categories if type is not None: qs = qs.filter(type__identifier=type) return qs def resolve_plan_categories(self, info, **kwargs): qs = Category.objects.all() plan = kwargs.get('plan') if plan is not None: qs = qs.filter(type__plan__identifier=plan) category_type = kwargs.get('category_type') if category_type is not None: qs = qs.filter(type__identifier=category_type) return gql_optimizer.query(qs, info) def resolve_organizations(self, info, plan, with_ancestors, **kwargs): qs = Organization.objects.all() if plan is not None: qs = qs.filter( responsible_actions__action__plan__identifier=plan).distinct() if with_ancestors: # Retrieving ancestors for a queryset doesn't seem to work, # so iterate over parents until we have a set of model IDs # for all children and their ancestors. if plan is None: raise GraphQLError( "withAncestors can only be used when 'plan' is set", [info]) all_ids = set() while True: vals = qs.values_list('id', 'parent') ids = set((val[0] for val in vals)) parent_ids = set((val[1] for val in vals)) all_ids.update(ids) if parent_ids.issubset(all_ids): break qs = Organization.objects.filter(id__in=parent_ids) qs = Organization.objects.filter(id__in=all_ids) return gql_optimizer.query(qs, info) def resolve_plan_indicators(self, info, plan, first=None, order_by=None, has_data=None, has_goals=None, **kwargs): qs = Indicator.objects.all() qs = qs.filter(levels__plan__identifier=plan).distinct() if has_data is not None: qs = qs.filter(latest_value__isnull=not has_data) if has_goals is not None: qs = qs.filter(goals__plan__identifier=plan).distinct() qs = order_queryset(qs, IndicatorNode, order_by) if first is not None: qs = qs[0:first] return gql_optimizer.query(qs, info) def resolve_action(self, info, **kwargs): obj_id = kwargs.get('id') identifier = kwargs.get('identifier') plan = kwargs.get('plan') if identifier and not plan: raise GraphQLError( "You must supply the 'plan' argument when using 'identifier'", [info]) qs = Action.objects.all() if obj_id: qs = qs.filter(id=obj_id) if identifier: qs = qs.filter(identifier=identifier, plan__identifier=plan) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except Action.DoesNotExist: return None return obj def resolve_person(self, info, **kwargs): qs = Person.objects.all() obj_id = kwargs.get('id') qs = qs.filter(id=obj_id) try: obj = qs.get() except Person.DoesNotExist: return None return obj def resolve_indicator(self, info, **kwargs): obj_id = kwargs.get('id') identifier = kwargs.get('identifier') plan = kwargs.get('plan') if not identifier and not obj_id: raise GraphQLError("You must supply either 'id' or 'identifier'", [info]) qs = Indicator.objects.all() if obj_id: qs = qs.filter(id=obj_id) if plan: qs = qs.filter(levels__plan__identifier=plan).distinct() if identifier: qs = qs.filter(identifier=identifier) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except Indicator.DoesNotExist: return None return obj def resolve_static_page(self, info, **kwargs): slug = kwargs.get('slug') plan = kwargs.get('plan') if not slug or not plan: raise GraphQLError("You must supply both 'slug' and 'plan'", [info]) qs = StaticPage.objects.all() qs = qs.filter(slug=slug, plan__identifier=plan) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except StaticPage.DoesNotExist: return None return obj
class ProductVariant(CountableDjangoObjectType): stock_quantity = graphene.Int( required=True, description='Quantity of a product available for sale.') price_override = graphene.Field( Money, description=dedent( """Override the base price of a product if necessary. A value of `null` indicates that the default product price is used.""")) price = graphene.Field(Money, description='Price of the product variant.') attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description='List of attributes assigned to this variant.') cost_price = graphene.Field(Money, description='Cost price of the variant.') margin = graphene.Int(description='Gross margin percentage value.') quantity_ordered = graphene.Int(description='Total quantity ordered.') revenue = graphene.Field( TaxedMoney, period=graphene.Argument(ReportingPeriod), description=dedent('''Total revenue generated by a variant in given period of time. Note: this field should be queried using `reportProductSales` query as it uses optimizations suitable for such calculations.''')) images = gql_optimizer.field(graphene.List( lambda: ProductImage, description='List of images for the product variant'), model_field='images') translation = graphene.Field( ProductVariantTranslation, language_code=graphene.Argument( LanguageCodeEnum, description='A language code to return the translation for.', required=True), description=('Returns translated Product Variant fields ' 'for the given language code.'), resolver=resolve_translation) digital_content = gql_optimizer.field(graphene.Field( DigitalContent, description='Digital content for the product variant'), model_field='digital_content') class Meta: description = dedent("""Represents a version of a product such as different size or color.""") only_fields = [ 'id', 'name', 'product', 'quantity', 'quantity_allocated', 'sku', 'track_inventory', 'weight' ] interfaces = [relay.Node] model = models.ProductVariant @permission_required('product.manage_products') def resolve_digital_content(self, *_args): return getattr(self, 'digital_content', None) def resolve_stock_quantity(self, *_args): return self.quantity_available @gql_optimizer.resolver_hints( prefetch_related='product__product_type__variant_attributes__values') def resolve_attributes(self, *_args): attributes_qs = self.product.product_type.variant_attributes.all() return resolve_attribute_list(self.attributes, attributes_qs) @permission_required('product.manage_products') def resolve_margin(self, *_args): return get_margin_for_variant(self) def resolve_price(self, *_args): return (self.price_override if self.price_override is not None else self.product.price) @permission_required('product.manage_products') def resolve_price_override(self, *_args): return self.price_override @permission_required('product.manage_products') def resolve_quantity(self, *_args): return self.quantity @permission_required(['order.manage_orders', 'product.manage_products']) def resolve_quantity_ordered(self, *_args): # This field is added through annotation when using the # `resolve_report_product_sales` resolver. return getattr(self, 'quantity_ordered', None) @permission_required(['order.manage_orders', 'product.manage_products']) def resolve_quantity_allocated(self, *_args): return self.quantity_allocated @permission_required(['order.manage_orders', 'product.manage_products']) def resolve_revenue(self, *_args, period): start_date = reporting_period_to_date(period) return calculate_revenue_for_variant(self, start_date) def resolve_images(self, *_args): return self.images.all() @classmethod def get_node(cls, info, id): user = info.context.user visible_products = models.Product.objects.visible_to_user( user).values_list('pk', flat=True) try: return cls._meta.model.objects.filter( product__id__in=visible_products).get(pk=id) except cls._meta.model.DoesNotExist: return None
class Category(CountableDjangoObjectType): ancestors = PrefetchingConnectionField( lambda: Category, description='List of ancestors of the category.') products = gql_optimizer.field(PrefetchingConnectionField( Product, description='List of products in the category.'), prefetch_related=prefetch_products) url = graphene.String( description='The storefront\'s URL for the category.') children = PrefetchingConnectionField( lambda: Category, description='List of children of the category.') background_image = graphene.Field( Image, size=graphene.Int(description='Size of the image')) translation = graphene.Field( CategoryTranslation, language_code=graphene.Argument( LanguageCodeEnum, description='A language code to return the translation for.', required=True), description=( 'Returns translated Category fields for the given language code.'), resolver=resolve_translation) class Meta: description = dedent("""Represents a single category of products. Categories allow to organize products in a tree-hierarchies which can be used for navigation in the storefront.""") only_fields = [ 'description', 'description_json', 'id', 'level', 'name', 'parent', 'seo_description', 'seo_title', 'slug' ] interfaces = [relay.Node] model = models.Category def resolve_ancestors(self, info, **_kwargs): qs = self.get_ancestors() return gql_optimizer.query(qs, info) def resolve_background_image(self, info, size=None, **_kwargs): if self.background_image: return Image.get_adjusted( image=self.background_image, alt=self.background_image_alt, size=size, rendition_key_set='background_images', info=info, ) def resolve_children(self, info, **_kwargs): qs = self.children.all() return gql_optimizer.query(qs, info) def resolve_url(self, _info): return self.get_absolute_url() def resolve_products(self, info, **_kwargs): # If the category has no children, we use the prefetched data. children = self.children.all() if not children and hasattr(self, 'prefetched_products'): return self.prefetched_products # Otherwise we want to include products from child categories which # requires performing additional logic. tree = self.get_descendants(include_self=True) qs = models.Product.objects.published() qs = qs.filter(category__in=tree) return gql_optimizer.query(qs, info)
class Product(CountableDjangoObjectType): url = graphene.String( description='The storefront URL for the product.', required=True) thumbnail_url = graphene.String( description='The URL of a main thumbnail for a product.', size=graphene.Argument(graphene.Int, description='Size of thumbnail')) availability = graphene.Field( ProductAvailability, description=dedent("""Informs about product's availability in the storefront, current price and discounts.""")) price = graphene.Field( Money, description=dedent("""The product's base price (without any discounts applied).""")) attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description='List of attributes assigned to this product.') purchase_cost = graphene.Field(MoneyRange) margin = graphene.Field(Margin) image_by_id = graphene.Field( lambda: ProductImage, id=graphene.Argument( graphene.ID, description='ID of a product image.'), description='Get a single product image by ID') variants = gql_optimizer.field( graphene.List( ProductVariant, description='List of varinats for the product'), model_field='variants') images = gql_optimizer.field( graphene.List( lambda: ProductImage, description='List of images for the product'), model_field='images') collections = gql_optimizer.field( graphene.List( lambda: Collection, description='List of collections for the product'), model_field='collections') class Meta: description = dedent("""Represents an individual item for sale in the storefront.""") interfaces = [relay.Node] model = models.Product exclude_fields = ['voucher_set', 'sale_set'] @gql_optimizer.resolver_hints(prefetch_related='images') def resolve_thumbnail_url(self, info, *, size=None): if not size: size = 255 url = get_thumbnail(self.get_first_image(), size, method='thumbnail') return info.context.build_absolute_uri(url) def resolve_url(self, info): return self.get_absolute_url() @gql_optimizer.resolver_hints( prefetch_related='variants', only=['available_on', 'charge_taxes', 'price', 'tax_rate']) def resolve_availability(self, info): context = info.context availability = get_availability( self, context.discounts, context.taxes, context.currency) return ProductAvailability(**availability._asdict()) @gql_optimizer.resolver_hints( prefetch_related='product_type__product_attributes__values') def resolve_attributes(self, info): attributes_qs = self.product_type.product_attributes.all() return resolve_attribute_list(self.attributes, attributes_qs) @permission_required('product.manage_products') def resolve_purchase_cost(self, info): purchase_cost, _ = get_product_costs_data(self) return purchase_cost @permission_required('product.manage_products') def resolve_margin(self, info): _, margin = get_product_costs_data(self) return Margin(margin[0], margin[1]) def resolve_image_by_id(self, info, id): pk = get_database_id(info, id, ProductImage) try: return self.images.get(pk=pk) except models.ProductImage.DoesNotExist: raise GraphQLError('Product image not found.') @gql_optimizer.resolver_hints(model_field='images') def resolve_images(self, info, **kwargs): return self.images.all() def resolve_variants(self, info, **kwargs): return self.variants.all() def resolve_collections(self, info): return self.collections.all()
class Product(CountableDjangoObjectType): url = graphene.String( description="The storefront URL for the product.", required=True, deprecation_reason="This field will be removed after 2020-07-31.", ) thumbnail = graphene.Field( Image, description="The main thumbnail for a product.", size=graphene.Argument(graphene.Int, description="Size of thumbnail."), ) pricing = graphene.Field( ProductPricingInfo, description= ("Lists the storefront product's pricing, the current price and discounts, " "only meant for displaying."), ) is_available = graphene.Boolean( description="Whether the product is in stock and visible or not.") base_price = graphene.Field( Money, description="The product's default base price.") minimal_variant_price = graphene.Field( Money, description="The price of the cheapest variant (including discounts).") tax_type = graphene.Field( TaxType, description="A type of tax. Assigned by enabled tax gateway") attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description="List of attributes assigned to this product.", ) purchase_cost = graphene.Field(MoneyRange) margin = graphene.Field(Margin) image_by_id = graphene.Field( lambda: ProductImage, id=graphene.Argument(graphene.ID, description="ID of a product image."), description="Get a single product image by ID.", ) variants = gql_optimizer.field( graphene.List(ProductVariant, description="List of variants for the product."), model_field="variants", ) images = gql_optimizer.field( graphene.List(lambda: ProductImage, description="List of images for the product."), model_field="images", ) collections = gql_optimizer.field( graphene.List(lambda: Collection, description="List of collections for the product."), model_field="collections", ) translation = TranslationField(ProductTranslation, type_name="product") class Meta: description = "Represents an individual item for sale in the storefront." interfaces = [relay.Node, ObjectWithMetadata] model = models.Product only_fields = [ "category", "charge_taxes", "description", "description_json", "id", "is_published", "name", "slug", "product_type", "publication_date", "seo_description", "seo_title", "updated_at", "weight", ] @staticmethod def resolve_tax_type(root: models.Product, info): tax_data = info.context.plugins.get_tax_code_from_object_meta(root) return TaxType(tax_code=tax_data.code, description=tax_data.description) @staticmethod @gql_optimizer.resolver_hints(prefetch_related="images") def resolve_thumbnail(root: models.Product, info, *, size=255): image = root.get_first_image() if image: url = get_product_image_thumbnail(image, size, method="thumbnail") alt = image.alt return Image(alt=alt, url=info.context.build_absolute_uri(url)) return None @staticmethod def resolve_url(root: models.Product, *_args): return "" @staticmethod @gql_optimizer.resolver_hints( prefetch_related=("variants", "collections"), only=[ "publication_date", "charge_taxes", "price_amount", "currency", "metadata", ], ) def resolve_pricing(root: models.Product, info): context = info.context availability = get_product_availability( root, context.discounts, context.country, context.currency, context.plugins, ) return ProductPricingInfo(**asdict(availability)) @staticmethod @gql_optimizer.resolver_hints(prefetch_related=("variants")) def resolve_is_available(root: models.Product, info): country = info.context.country in_stock = is_product_in_stock(root, country) return root.is_visible and in_stock @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_base_price(root: models.Product, _info): return root.price @staticmethod @gql_optimizer.resolver_hints( prefetch_related=("variants", "collections"), only=[ "publication_date", "charge_taxes", "price_amount", "currency", "metadata", ], ) def resolve_price(root: models.Product, info): price_range = root.get_price_range(info.context.discounts) price = info.context.plugins.apply_taxes_to_product( root, price_range.start, info.context.country) return price.net @staticmethod @gql_optimizer.resolver_hints(prefetch_related=[ Prefetch( "product_type__attributeproduct", queryset=models.AttributeProduct.objects.filter( attribute__visible_in_storefront=True).prefetch_related( "productassignments__values", "attribute"), to_attr="storefront_attributes", ) ]) def resolve_attributes(root: models.Product, info): return resolve_attribute_list(root, user=info.context.user) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_purchase_cost(root: models.Product, *_args): purchase_cost, _ = get_product_costs_data(root) return purchase_cost @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_margin(root: models.Product, *_args): _, margin = get_product_costs_data(root) return Margin(margin[0], margin[1]) @staticmethod def resolve_image_by_id(root: models.Product, info, id): pk = get_database_id(info, id, ProductImage) try: return root.images.get(pk=pk) except models.ProductImage.DoesNotExist: raise GraphQLError("Product image not found.") @staticmethod @gql_optimizer.resolver_hints(model_field="images") def resolve_images(root: models.Product, *_args, **_kwargs): return root.images.all() @staticmethod def resolve_variants(root: models.Product, *_args, **_kwargs): return root.variants.all() @staticmethod def resolve_collections(root: models.Product, *_args): return root.collections.all() @classmethod def get_node(cls, info, pk): if info.context: qs = cls._meta.model.objects.visible_to_user(info.context.user) return cls.maybe_optimize(info, qs, pk) return None @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_private_meta(root: models.Product, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.Product, _info): return resolve_meta(root, _info) @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class ProductVariant(CountableDjangoObjectType, MetadataObjectType): stock_quantity = graphene.Int( required=True, description="Quantity of a product available for sale.") price_override = graphene.Field( Money, description= ("Override the base price of a product if necessary. A value of `null` " "indicates that the default product price is used."), ) price = graphene.Field( Money, description="Price of the product variant.", deprecation_reason=( "DEPRECATED: Will be removed in Saleor 2.10, " "has been replaced by 'pricing.priceUndiscounted'"), ) availability = graphene.Field( VariantPricingInfo, description= ("Informs about variant's availability in the storefront, current price and " "discounted price."), deprecation_reason= ("DEPRECATED: Will be removed in Saleor 2.10, has been renamed to `pricing`." ), ) pricing = graphene.Field( VariantPricingInfo, description= ("Lists the storefront variant's pricing, the current price and discounts, " "only meant for displaying."), ) is_available = graphene.Boolean( description="Whether the variant is in stock and visible or not.") attributes = gql_optimizer.field( graphene.List( graphene.NonNull(SelectedAttribute), required=True, description="List of attributes assigned to this variant.", )) cost_price = graphene.Field(Money, description="Cost price of the variant.") margin = graphene.Int(description="Gross margin percentage value.") quantity_ordered = graphene.Int(description="Total quantity ordered.") revenue = graphene.Field( TaxedMoney, period=graphene.Argument(ReportingPeriod), description= ("Total revenue generated by a variant in given period of time. Note: this " "field should be queried using `reportProductSales` query as it uses " "optimizations suitable for such calculations."), ) images = gql_optimizer.field( graphene.List(lambda: ProductImage, description="List of images for the product variant."), model_field="images", ) translation = graphene.Field( ProductVariantTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description=("Returns translated Product Variant fields " "for the given language code."), resolver=resolve_translation, ) digital_content = gql_optimizer.field( graphene.Field(DigitalContent, description="Digital content for the product variant."), model_field="digital_content", ) class Meta: description = ( "Represents a version of a product such as different size or color." ) only_fields = [ "id", "name", "product", "quantity", "quantity_allocated", "sku", "track_inventory", "weight", ] interfaces = [relay.Node] model = models.ProductVariant @staticmethod @permission_required("product.manage_products") def resolve_digital_content(root: models.ProductVariant, *_args): return getattr(root, "digital_content", None) @staticmethod def resolve_stock_quantity(root: models.ProductVariant, *_args): return root.quantity_available @staticmethod @gql_optimizer.resolver_hints(prefetch_related=[ "attributes__values", "attributes__assignment__attribute" ]) def resolve_attributes(root: models.ProductVariant, info): return resolve_attribute_list(root, user=info.context.user) @staticmethod @permission_required("product.manage_products") def resolve_margin(root: models.ProductVariant, *_args): return get_margin_for_variant(root) @staticmethod def resolve_price(root: models.ProductVariant, *_args): return (root.price_override if root.price_override is not None else root.product.price) @staticmethod @gql_optimizer.resolver_hints(prefetch_related=("product", ), only=["price_override_amount", "currency"]) def resolve_pricing(root: models.ProductVariant, info): context = info.context availability = get_variant_availability( root, context.discounts, context.country, context.currency, extensions=context.extensions, ) return VariantPricingInfo(**availability._asdict()) resolve_availability = resolve_pricing @staticmethod def resolve_is_available(root: models.ProductVariant, _info): return root.is_available @staticmethod @permission_required("product.manage_products") def resolve_price_override(root: models.ProductVariant, *_args): return root.price_override @staticmethod @permission_required("product.manage_products") def resolve_quantity(root: models.ProductVariant, *_args): return root.quantity @staticmethod @permission_required(["order.manage_orders", "product.manage_products"]) def resolve_quantity_ordered(root: models.ProductVariant, *_args): # This field is added through annotation when using the # `resolve_report_product_sales` resolver. return getattr(root, "quantity_ordered", None) @staticmethod @permission_required(["order.manage_orders", "product.manage_products"]) def resolve_quantity_allocated(root: models.ProductVariant, *_args): return root.quantity_allocated @staticmethod @permission_required(["order.manage_orders", "product.manage_products"]) def resolve_revenue(root: models.ProductVariant, *_args, period): start_date = reporting_period_to_date(period) return calculate_revenue_for_variant(root, start_date) @staticmethod def resolve_images(root: models.ProductVariant, *_args): return root.images.all() @classmethod def get_node(cls, info, id): user = info.context.user visible_products = models.Product.objects.visible_to_user( user).values_list("pk", flat=True) qs = cls._meta.model.objects.filter(product__id__in=visible_products) return cls.maybe_optimize(info, qs, id) @staticmethod @permission_required("product.manage_products") def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info)
class Category(CountableDjangoObjectType, MetadataObjectType): ancestors = PrefetchingConnectionField( lambda: Category, description="List of ancestors of the category.") products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products in the category."), prefetch_related=prefetch_products, ) url = graphene.String(description="The storefront's URL for the category.") children = PrefetchingConnectionField( lambda: Category, description="List of children of the category.") background_image = graphene.Field( Image, size=graphene.Int(description="Size of the image.")) translation = graphene.Field( CategoryTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description=( "Returns translated Category fields for the given language code."), resolver=resolve_translation, ) class Meta: description = ( "Represents a single category of products. Categories allow to organize " "products in a tree-hierarchies which can be used for navigation in the " "storefront.") only_fields = [ "description", "description_json", "id", "level", "name", "parent", "seo_description", "seo_title", "slug", ] interfaces = [relay.Node] model = models.Category @staticmethod def resolve_ancestors(root: models.Category, info, **_kwargs): qs = root.get_ancestors() return gql_optimizer.query(qs, info) @staticmethod def resolve_background_image(root: models.Category, info, size=None, **_kwargs): if root.background_image: return Image.get_adjusted( image=root.background_image, alt=root.background_image_alt, size=size, rendition_key_set="background_images", info=info, ) @staticmethod def resolve_children(root: models.Category, info, **_kwargs): qs = root.children.all() return gql_optimizer.query(qs, info) @staticmethod def resolve_url(root: models.Category, _info): return root.get_absolute_url() @staticmethod def resolve_products(root: models.Category, info, **_kwargs): # If the category has no children, we use the prefetched data. children = root.children.all() if not children and hasattr(root, "prefetched_products"): return root.prefetched_products # Otherwise we want to include products from child categories which # requires performing additional logic. tree = root.get_descendants(include_self=True) qs = models.Product.objects.published() qs = qs.filter(category__in=tree) return gql_optimizer.query(qs, info) @staticmethod @permission_required("product.manage_products") def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info)
class Order(CountableDjangoObjectType): fulfillments = gql_optimizer.field(graphene.List( Fulfillment, required=True, description='List of shipments for the order.'), model_field='fulfillments') lines = gql_optimizer.field(graphene.List( lambda: OrderLine, required=True, description='List of order lines.'), model_field='lines') is_paid = graphene.Boolean( description='Informs if an order is fully paid.') number = graphene.String(description='User-friendly number of an order.') payment_status = PaymentStatusEnum(description='Internal payment status.') payment_status_display = graphene.String( description='User-friendly payment status.') total = graphene.Field(TaxedMoney, description='Total amount of the order.') shipping_price = graphene.Field(TaxedMoney, description='Total price of shipping.') subtotal = graphene.Field( TaxedMoney, description='The sum of line prices not including shipping.') status_display = graphene.String(description='User-friendly order status.') total_authorized = graphene.Field( Money, description='Amount authorized for the order.') total_captured = graphene.Field(Money, description='Amount captured by payment.') events = gql_optimizer.field(graphene.List( OrderEvent, description='List of events associated with the order.'), model_field='events') user_email = graphene.String(required=False, description='Email address of the customer.') available_shipping_methods = graphene.List( ShippingMethod, required=False, description='Shipping methods that can be used with this order.') class Meta: description = 'Represents an order in the shop.' interfaces = [relay.Node] model = models.Order exclude_fields = [ 'shipping_price_gross', 'shipping_price_net', 'total_gross', 'total_net' ] @staticmethod def resolve_shipping_price(obj, info): return obj.shipping_price @staticmethod def resolve_subtotal(obj, info): return obj.get_subtotal() @staticmethod def resolve_total(obj, info): return obj.total @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_total_authorized(obj, info): payment = obj.get_last_payment() if payment: return payment.get_total().gross @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_total_captured(obj, info): payment = obj.get_last_payment() if payment: return payment.get_captured_price() @staticmethod def resolve_fulfillments(obj, info): return obj.fulfillments.all() @staticmethod def resolve_lines(obj, info): return obj.lines.all() @staticmethod def resolve_events(obj, info): return obj.events.all() @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_is_paid(obj, info): return obj.is_fully_paid() @staticmethod def resolve_number(obj, info): return str(obj.pk) @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_payment_status(obj, info): return obj.get_last_payment_status() @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_payment_status_display(obj, info): return obj.get_last_payment_status_display() @staticmethod def resolve_status_display(obj, info): return obj.get_status_display() @staticmethod def resolve_user_email(obj, info): if obj.user_email: return obj.user_email if obj.user_id: return obj.user.email return None @staticmethod def resolve_available_shipping_methods(obj, info): if not obj.is_shipping_required(): return None if not obj.shipping_address: return None qs = shipping_models.ShippingMethod.objects qs = qs.applicable_shipping_methods( price=obj.get_subtotal().gross.amount, weight=obj.get_total_weight(), country_code=obj.shipping_address.country.code) return qs
class Order(CountableDjangoObjectType): fulfillments = gql_optimizer.field(graphene.List( Fulfillment, required=True, description='List of shipments for the order.'), model_field='fulfillments') lines = gql_optimizer.field(graphene.List( lambda: OrderLine, required=True, description='List of order lines.'), model_field='lines') actions = graphene.List( OrderAction, description='''List of actions that can be performed in the current state of an order.''', required=True) available_shipping_methods = graphene.List( ShippingMethod, required=False, description='Shipping methods that can be used with this order.') number = graphene.String(description='User-friendly number of an order.') is_paid = graphene.Boolean( description='Informs if an order is fully paid.') payment_status = PaymentChargeStatusEnum( description='Internal payment status.') payment_status_display = graphene.String( description='User-friendly payment status.') payments = gql_optimizer.field(graphene.List( Payment, description='List of payments for the order'), model_field='payments') total = graphene.Field(TaxedMoney, description='Total amount of the order.') shipping_price = graphene.Field(TaxedMoney, description='Total price of shipping.') subtotal = graphene.Field( TaxedMoney, description='The sum of line prices not including shipping.') status_display = graphene.String(description='User-friendly order status.') can_finalize = graphene.Boolean( description=('Informs whether a draft order can be finalized', '(turned into a regular order).'), required=True) total_authorized = graphene.Field( Money, description='Amount authorized for the order.') total_captured = graphene.Field(Money, description='Amount captured by payment.') events = gql_optimizer.field(graphene.List( OrderEvent, description='List of events associated with the order.'), model_field='events') total_balance = graphene.Field( Money, description='''The difference between the paid and the order total amount.''', required=True) user_email = graphene.String(required=False, description='Email address of the customer.') is_shipping_required = graphene.Boolean( description='Returns True, if order requires shipping.', required=True) lines = graphene.List(OrderLine, required=True, description='List of order lines for the order') class Meta: description = 'Represents an order in the shop.' interfaces = [relay.Node] model = models.Order exclude_fields = [ 'shipping_price_gross', 'shipping_price_net', 'total_gross', 'total_net' ] @staticmethod def resolve_shipping_price(self, info): return self.shipping_price @gql_optimizer.resolver_hints(prefetch_related='payments__transactions') def resolve_actions(self, info): actions = [] payment = self.get_last_payment() if self.can_capture(payment): actions.append(OrderAction.CAPTURE) if self.can_mark_as_paid(): actions.append(OrderAction.MARK_AS_PAID) if self.can_refund(payment): actions.append(OrderAction.REFUND) if self.can_void(payment): actions.append(OrderAction.VOID) return actions @staticmethod def resolve_subtotal(self, info): return self.get_subtotal() @staticmethod def resolve_total(self, info): return self.total @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments__transactions') def resolve_total_authorized(self, info): # FIXME adjust to multiple payments in the future return self.total_authorized @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_total_captured(self, info): # FIXME adjust to multiple payments in the future return self.total_captured @staticmethod def resolve_total_balance(self, info): return self.total_balance @staticmethod def resolve_fulfillments(self, info): return self.fulfillments.all().order_by('pk') @staticmethod def resolve_lines(self, info): return self.lines.all().order_by('pk') @staticmethod def resolve_events(self, info): return self.events.all().order_by('pk') @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_is_paid(self, info): return self.is_fully_paid() @staticmethod def resolve_number(self, info): return str(self.pk) @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_payment_status(self, info): return self.get_last_payment_status() @staticmethod @gql_optimizer.resolver_hints(prefetch_related='payments') def resolve_payment_status_display(self, info): return self.get_last_payment_status_display() @staticmethod def resolve_payments(self, info): return self.payments.all() @staticmethod def resolve_status_display(self, info): return self.get_status_display() @staticmethod def resolve_can_finalize(self, info): errors = can_finalize_draft_order(self, []) return not errors @staticmethod def resolve_user_email(self, info): if self.user_email: return self.user_email if self.user_id: return self.user.email return None @staticmethod def resolve_available_shipping_methods(self, info): from .resolvers import resolve_shipping_methods return resolve_shipping_methods(self, info, self.get_subtotal().gross.amount) def resolve_is_shipping_required(self, info): return self.is_shipping_required()
class Checkout(CountableDjangoObjectType): available_shipping_methods = graphene.List( ShippingMethod, required=True, description="Shipping methods that can be used with this order.", ) available_payment_gateways = graphene.List( PaymentGateway, description="List of available payment gateways.", required=True ) email = graphene.String(description="Email of a customer.", required=True) gift_cards = gql_optimizer.field( graphene.List( GiftCard, description="List of gift cards associated with this checkout." ), model_field="gift_cards", ) is_shipping_required = graphene.Boolean( description="Returns True, if checkout requires shipping.", required=True ) lines = gql_optimizer.field( graphene.List( CheckoutLine, description=( "A list of checkout lines, each containing information about " "an item in the checkout." ), ), model_field="lines", ) shipping_price = graphene.Field( TaxedMoney, description="The price of the shipping, with all the taxes included.", ) subtotal_price = graphene.Field( TaxedMoney, description="The price of the checkout before shipping, with taxes included.", ) total_price = graphene.Field( TaxedMoney, description=( "The sum of the the checkout line prices, with all the taxes," "shipping costs, and discounts included." ), ) class Meta: only_fields = [ "billing_address", "created", "discount_name", "gift_cards", "is_shipping_required", "last_change", "note", "quantity", "shipping_address", "shipping_method", "token", "translated_discount_name", "user", "voucher_code", "discount", ] description = "Checkout object." model = models.Checkout interfaces = [graphene.relay.Node, ObjectWithMetadata] filter_fields = ["token"] @staticmethod def resolve_user(root: models.Checkout, info): user = info.context.user
class Voucher(CountableDjangoObjectType): categories = gql_optimizer.field( PrefetchingConnectionField( Category, description="List of categories this voucher applies to."), model_field="categories", ) collections = gql_optimizer.field( PrefetchingConnectionField( Collection, description="List of collections this voucher applies to."), model_field="collections", ) products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products this voucher applies to."), model_field="products", ) discount_type = DiscountTypeEnum( description= "Determines a type of discount for voucher - value or percentage", required=True, ) type = VoucherTypeEnum(description="Determines a type of voucher.", required=True) class Meta: description = ( "Vouchers allow giving discounts to particular customers on categories, " "collections or specific products. They can be used during checkouts by " "providing valid voucher codes.") only_fields = [ "id", "type", "code", "usage_limit", "used", "start_date", "end_date", "apply_once_per_order", "apply_once_per_customer", "discount_type", "discount_value", "min_spent_amount", "min_checkout_items_quantity", ] interfaces = [relay.Node] model = models.Voucher @staticmethod def resolve_categories(root: models.Voucher, *_args, **_kwargs): return root.categories.all() @staticmethod def resolve_collections(root: models.Voucher, info, **_kwargs): return root.collections.visible_to_user( info.context.user, CollectionPermissions.MANAGE_COLLECTIONS) @staticmethod def resolve_products(root: models.Voucher, info, **_kwargs): return root.products.visible_to_user( info.context.user, ProductPermissions.MANAGE_PRODUCTS)
class User(MetadataObjectType, CountableDjangoObjectType): addresses = gql_optimizer.field( graphene.List(Address, description="List of all user's addresses."), model_field="addresses", ) checkout = graphene.Field( Checkout, description="Returns the last open checkout of this user." ) gift_cards = gql_optimizer.field( PrefetchingConnectionField( "saleor.graphql.giftcard.types.GiftCard", description="List of the user gift cards.", ), model_field="gift_cards", ) note = graphene.String(description="A note about the customer.") orders = gql_optimizer.field( PrefetchingConnectionField( "saleor.graphql.order.types.Order", description="List of user's orders." ), model_field="orders", ) permissions = graphene.List( PermissionDisplay, description="List of user's permissions." ) avatar = graphene.Field(Image, size=graphene.Int(description="Size of the avatar.")) events = gql_optimizer.field( graphene.List( CustomerEvent, description="List of events associated with the user." ), model_field="events", ) stored_payment_sources = graphene.List( "saleor.graphql.payment.types.PaymentSource", description="List of stored payment sources.", ) class Meta: description = "Represents user data." interfaces = [relay.Node] model = get_user_model() only_fields = [ "date_joined", "default_billing_address", "default_shipping_address", "email", "first_name", "id", "is_active", "is_staff", "last_login", "last_name", "note", "token", ] @staticmethod def resolve_addresses(root: models.User, _info, **_kwargs): return root.addresses.annotate_default(root).all() @staticmethod def resolve_checkout(root: models.User, _info, **_kwargs): return get_user_checkout(root)[0] @staticmethod def resolve_gift_cards(root: models.User, info, **_kwargs): return root.gift_cards.all() @staticmethod def resolve_permissions(root: models.User, _info, **_kwargs): if root.is_superuser: permissions = get_permissions() else: permissions = root.user_permissions.prefetch_related( "content_type" ).order_by("codename") return format_permissions_for_display(permissions) @staticmethod @one_of_permissions_required(["account.manage_users", "account.manage_staff"]) def resolve_note(root: models.User, info): return root.note @staticmethod @one_of_permissions_required(["account.manage_users", "account.manage_staff"]) def resolve_events(root: models.User, info): return root.events.all() @staticmethod def resolve_orders(root: models.User, info, **_kwargs): viewer = info.context.user if viewer.has_perm("order.manage_orders"): return root.orders.all() return root.orders.confirmed() @staticmethod def resolve_avatar(root: models.User, info, size=None, **_kwargs): if root.avatar: return Image.get_adjusted( image=root.avatar, alt=None, size=size, rendition_key_set="user_avatars", info=info, ) @staticmethod def resolve_stored_payment_sources(root: models.User, info): from .resolvers import resolve_payment_sources if root == info.context.user: return resolve_payment_sources(root) raise PermissionDenied() @staticmethod @one_of_permissions_required(["account.manage_users", "account.manage_staff"]) def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info) @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class Checkout(MetadataObjectType, CountableDjangoObjectType): available_shipping_methods = graphene.List( ShippingMethod, required=True, description="Shipping methods that can be used with this order.", ) available_payment_gateways = graphene.List( PaymentGatewayEnum, description="List of available payment gateways.", required=True, ) email = graphene.String(description="Email of a customer", required=True) gift_cards = gql_optimizer.field( graphene.List( GiftCard, description="List of gift cards associated with this checkout"), model_field="gift_cards", ) is_shipping_required = graphene.Boolean( description="Returns True, if checkout requires shipping.", required=True) lines = gql_optimizer.field( graphene.List( CheckoutLine, description=( "A list of checkout lines, each containing information about " "an item in the checkout."), ), model_field="lines", ) shipping_price = graphene.Field( TaxedMoney, description="The price of the shipping, with all the taxes included.", ) subtotal_price = graphene.Field( TaxedMoney, description= "The price of the checkout before shipping, with taxes included.", ) total_price = graphene.Field( TaxedMoney, description=( "The sum of the the checkout line prices, with all the taxes," "shipping costs, and discounts included."), ) discount_amount = graphene.Field( Money, deprecation_reason="Use discount instead.") class Meta: only_fields = [ "billing_address", "created", "discount_amount", "discount_name", "gift_cards", "is_shipping_required", "last_change", "note", "quantity", "shipping_address", "shipping_method", "token", "translated_discount_name", "user", "voucher_code", "discount", ] description = "Checkout object" model = models.Checkout interfaces = [graphene.relay.Node] filter_fields = ["token"] @staticmethod def resolve_email(root: models.Checkout, info): return root.get_customer_email() @staticmethod def resolve_total_price(root: models.Checkout, info): taxed_total = (info.context.extensions.calculate_checkout_total( checkout=root, discounts=info.context.discounts) - root.get_total_gift_cards_balance()) return max(taxed_total, zero_taxed_money()) @staticmethod def resolve_subtotal_price(root: models.Checkout, info): return info.context.extensions.calculate_checkout_subtotal( checkout=root, discounts=info.context.discounts) @staticmethod def resolve_shipping_price(root: models.Checkout, info): return info.context.extensions.calculate_checkout_shipping( checkout=root, discounts=info.context.discounts) @staticmethod def resolve_lines(root: models.Checkout, *_args): return root.lines.prefetch_related("variant") @staticmethod def resolve_available_shipping_methods(root: models.Checkout, info): available = get_valid_shipping_methods_for_checkout( root, info.context.discounts) if available is None: return [] return available @staticmethod def resolve_available_payment_gateways(_: models.Checkout, _info): return settings.CHECKOUT_PAYMENT_GATEWAYS.keys() @staticmethod def resolve_gift_cards(root: models.Checkout, _info): return root.gift_cards.all() @staticmethod def resolve_is_shipping_required(root: models.Checkout, _info): return root.is_shipping_required() @staticmethod @permission_required("order.manage_orders") def resolve_private_meta(root: models.Checkout, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.Checkout, _info): return resolve_meta(root, _info) @staticmethod def resolve_discount_amount(root: models.Checkout, _info): return root.discount
class Collection(CountableDjangoObjectType, MetadataObjectType): products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products in this collection."), prefetch_related=prefetch_products_collection_sorted, ) background_image = graphene.Field( Image, size=graphene.Int(description="Size of the image.")) translation = TranslationField(CollectionTranslation, type_name="collection") class Meta: description = "Represents a collection of products." only_fields = [ "description", "description_json", "id", "is_published", "name", "publication_date", "seo_description", "seo_title", "slug", ] interfaces = [relay.Node] model = models.Collection @staticmethod def resolve_background_image(root: models.Collection, info, size=None, **_kwargs): if root.background_image: return Image.get_adjusted( image=root.background_image, alt=root.background_image_alt, size=size, rendition_key_set="background_images", info=info, ) @staticmethod def resolve_products(root: models.Collection, info, **_kwargs): if hasattr(root, "prefetched_products"): return root.prefetched_products # type: ignore qs = root.products.collection_sorted(info.context.user) return gql_optimizer.query(qs, info) @classmethod def get_node(cls, info, id): if info.context: user = info.context.user qs = cls._meta.model.objects.visible_to_user(user) return cls.maybe_optimize(info, qs, id) return None @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info) @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class Product(CountableDjangoObjectType): url = graphene.String( description="The storefront URL for the product.", required=True ) thumbnail_url = graphene.String( description="The URL of a main thumbnail for a product.", size=graphene.Argument(graphene.Int, description="Size of thumbnail"), deprecation_reason=( """thumbnailUrl is deprecated, use thumbnail instead""" ), ) thumbnail = graphene.Field( Image, description="The main thumbnail for a product.", size=graphene.Argument(graphene.Int, description="Size of thumbnail"), ) availability = graphene.Field( ProductPricingInfo, description="""Informs about product's availability in the storefront, current price and discounts.""", deprecation_reason="Has been renamed to 'pricing'.", ) pricing = graphene.Field( ProductPricingInfo, description="""Lists the storefront product's pricing, the current price and discounts, only meant for displaying.""", ) is_available = graphene.Boolean( description="Whether the product is in stock and visible or not." ) base_price = graphene.Field(Money, description="The product's default base price.") price = graphene.Field( Money, description="The product's default base price.", deprecation_reason=("Has been replaced by 'basePrice'"), ) tax_rate = TaxRateType( description="A type of tax rate.", deprecation_reason=( "taxRate is deprecated. Use taxType to obtain taxCode for given tax gateway" ), ) tax_type = graphene.Field( TaxType, description="A type of tax. Assigned by enabled tax gateway" ) attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description="List of attributes assigned to this product.", ) purchase_cost = graphene.Field(MoneyRange) margin = graphene.Field(Margin) image_by_id = graphene.Field( lambda: ProductImage, id=graphene.Argument(graphene.ID, description="ID of a product image."), description="Get a single product image by ID", ) variants = gql_optimizer.field( graphene.List(ProductVariant, description="List of variants for the product"), model_field="variants", ) images = gql_optimizer.field( graphene.List( lambda: ProductImage, description="List of images for the product" ), model_field="images", ) collections = gql_optimizer.field( graphene.List( lambda: Collection, description="List of collections for the product" ), model_field="collections", ) available_on = graphene.Date( deprecation_reason=("availableOn is deprecated, use publicationDate instead") ) translation = graphene.Field( ProductTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description=("Returns translated Product fields for the given language code."), resolver=resolve_translation, ) class Meta: description = """Represents an individual item for sale in the storefront.""" interfaces = [relay.Node] model = models.Product only_fields = [ "category", "charge_taxes", "description", "description_json", "id", "is_published", "name", "product_type", "publication_date", "seo_description", "seo_title", "updated_at", "weight", ] @staticmethod def resolve_tax_rate(root: models.Product, _info, **_kwargs): # FIXME this resolver should be dropped after we drop tax_rate from API tax_rate = vatlayer_interface.get_tax_from_object_meta(root).code return tax_rate or None @staticmethod def resolve_tax_type(root: models.Product, _info): tax_data = tax_interface.get_tax_from_object_meta(root) return TaxType(tax_code=tax_data.code, description=tax_data.description) @staticmethod @gql_optimizer.resolver_hints(prefetch_related="images") def resolve_thumbnail_url(root: models.Product, info, *, size=None): if not size: size = 255 url = get_product_image_thumbnail( root.get_first_image(), size, method="thumbnail" ) return info.context.build_absolute_uri(url) @staticmethod @gql_optimizer.resolver_hints(prefetch_related="images") def resolve_thumbnail(root: models.Product, info, *, size=None): image = root.get_first_image() if not size: size = 255 url = get_product_image_thumbnail(image, size, method="thumbnail") url = info.context.build_absolute_uri(url) alt = image.alt if image else None return Image(alt=alt, url=url) @staticmethod def resolve_url(root: models.Product, *_args): return root.get_absolute_url() @staticmethod @gql_optimizer.resolver_hints( prefetch_related=("variants", "collections"), only=["publication_date", "charge_taxes", "price", "meta"], ) def resolve_pricing(root: models.Product, info): context = info.context availability = get_product_availability( root, context.discounts, context.country, context.currency, context.taxes ) return ProductPricingInfo(**availability._asdict()) resolve_availability = resolve_pricing @staticmethod def resolve_is_available(root: models.Product, _info): return root.is_available @staticmethod @permission_required("product.manage_products") def resolve_base_price(root: models.Product, _info): return root.price @staticmethod @gql_optimizer.resolver_hints( prefetch_related=("variants", "collections"), only=["publication_date", "charge_taxes", "price", "meta"], ) def resolve_price(root: models.Product, info): price_range = root.get_price_range(info.context.discounts) price = tax_interface.apply_taxes_to_product( root, price_range.start, info.context.country, taxes=info.context.taxes ) return price.net @staticmethod @gql_optimizer.resolver_hints( prefetch_related="product_type__product_attributes__values" ) def resolve_attributes(root: models.Product, *_args): attributes_qs = root.product_type.product_attributes.all() return resolve_attribute_list(root.attributes, attributes_qs) @staticmethod @permission_required("product.manage_products") def resolve_purchase_cost(root: models.Product, *_args): purchase_cost, _ = get_product_costs_data(root) return purchase_cost @staticmethod @permission_required("product.manage_products") def resolve_margin(root: models.Product, *_args): _, margin = get_product_costs_data(root) return Margin(margin[0], margin[1]) @staticmethod def resolve_image_by_id(root: models.Product, info, id): pk = get_database_id(info, id, ProductImage) try: return root.images.get(pk=pk) except models.ProductImage.DoesNotExist: raise GraphQLError("Product image not found.") @staticmethod @gql_optimizer.resolver_hints(model_field="images") def resolve_images(root: models.Product, *_args, **_kwargs): return root.images.all() @staticmethod def resolve_variants(root: models.Product, *_args, **_kwargs): return root.variants.all() @staticmethod def resolve_collections(root: models.Product, *_args): return root.collections.all() @staticmethod def resolve_available_on(root: models.Product, *_args): return root.publication_date @classmethod def get_node(cls, info, pk): if info.context: qs = cls._meta.model.objects.visible_to_user(info.context.user) return cls.maybe_optimize(info, qs, pk) return None
class Attribute(CountableDjangoObjectType, MetadataObjectType): input_type = AttributeInputTypeEnum( description=AttributeDescriptions.INPUT_TYPE) name = graphene.String(description=AttributeDescriptions.NAME) slug = graphene.String(description=AttributeDescriptions.SLUG) values = gql_optimizer.field( graphene.List(AttributeValue, description=AttributeDescriptions.VALUES), model_field="values", ) value_required = graphene.Boolean( description=AttributeDescriptions.VALUE_REQUIRED, required=True) visible_in_storefront = graphene.Boolean( description=AttributeDescriptions.VISIBLE_IN_STOREFRONT, required=True) filterable_in_storefront = graphene.Boolean( description=AttributeDescriptions.FILTERABLE_IN_STOREFRONT, required=True) filterable_in_dashboard = graphene.Boolean( description=AttributeDescriptions.FILTERABLE_IN_DASHBOARD, required=True) available_in_grid = graphene.Boolean( description=AttributeDescriptions.AVAILABLE_IN_GRID, required=True) translation = graphene.Field( AttributeTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description=("Returns translated Attribute fields " "for the given language code."), resolver=resolve_translation, ) storefront_search_position = graphene.Int( description=AttributeDescriptions.STOREFRONT_SEARCH_POSITION, required=True) class Meta: description = ( "Custom attribute of a product. Attributes can be assigned to products and " "variants at the product type level.") only_fields = ["id", "product_types", "product_variant_types"] interfaces = [relay.Node] model = models.Attribute @staticmethod def resolve_values(root: models.Attribute, *_args): return root.values.all() @staticmethod @permission_required("product.manage_products") def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info) @staticmethod @permission_required("product.manage_products") def resolve_value_required(root: models.Attribute, *_args): return root.value_required @staticmethod @permission_required("product.manage_products") def resolve_visible_in_storefront(root: models.Attribute, *_args): return root.visible_in_storefront @staticmethod @permission_required("product.manage_products") def resolve_filterable_in_storefront(root: models.Attribute, *_args): return root.filterable_in_storefront @staticmethod @permission_required("product.manage_products") def resolve_filterable_in_dashboard(root: models.Attribute, *_args): return root.filterable_in_dashboard @staticmethod @permission_required("product.manage_products") def resolve_storefront_search_position(root: models.Attribute, *_args): return root.storefront_search_position @staticmethod @permission_required("product.manage_products") def resolve_available_in_grid(root: models.Attribute, *_args): return root.available_in_grid
class Checkout(MetadataObjectType, CountableDjangoObjectType): available_shipping_methods = graphene.List( ShippingMethod, required=True, description="Shipping methods that can be used with this order.", ) available_payment_gateways = graphene.List( PaymentGateway, description="List of available payment gateways.", required=True) email = graphene.String(description="Email of a customer.", required=True) gift_cards = gql_optimizer.field( graphene.List( GiftCard, description="List of gift cards associated with this checkout."), model_field="gift_cards", ) is_shipping_required = graphene.Boolean( description="Returns True, if checkout requires shipping.", required=True) lines = gql_optimizer.field( graphene.List( CheckoutLine, description=( "A list of checkout lines, each containing information about " "an item in the checkout."), ), model_field="lines", ) shipping_price = graphene.Field( TaxedMoney, description="The price of the shipping, with all the taxes included.", ) subtotal_price = graphene.Field( TaxedMoney, description= "The price of the checkout before shipping, with taxes included.", ) total_price = graphene.Field( TaxedMoney, description=( "The sum of the the checkout line prices, with all the taxes," "shipping costs, and discounts included."), ) class Meta: only_fields = [ "billing_address", "created", "discount_name", "gift_cards", "is_shipping_required", "last_change", "note", "quantity", "shipping_address", "shipping_method", "token", "translated_discount_name", "user", "voucher_code", "discount", ] description = "Checkout object." model = models.Checkout interfaces = [graphene.relay.Node] filter_fields = ["token"] @staticmethod def resolve_email(root: models.Checkout, info): return root.get_customer_email() @staticmethod def resolve_total_price(root: models.Checkout, info): taxed_total = (calculations.checkout_total( checkout=root, discounts=info.context.discounts) - root.get_total_gift_cards_balance()) return max(taxed_total, zero_taxed_money()) @staticmethod def resolve_subtotal_price(root: models.Checkout, info): return calculations.checkout_subtotal(checkout=root, discounts=info.context.discounts) @staticmethod def resolve_shipping_price(root: models.Checkout, info): return calculations.checkout_shipping_price( checkout=root, discounts=info.context.discounts) @staticmethod def resolve_lines(root: models.Checkout, *_args): return root.lines.prefetch_related("variant") @staticmethod def resolve_available_shipping_methods(root: models.Checkout, info): available = get_valid_shipping_methods_for_checkout( root, info.context.discounts) if available is None: return [] manager = get_extensions_manager() display_gross = display_gross_prices() for shipping_method in available: # ignore mypy checking because it is checked in # get_valid_shipping_methods_for_checkout taxed_price = manager.apply_taxes_to_shipping( shipping_method.price, root.shipping_address # type: ignore ) if display_gross: shipping_method.price = taxed_price.gross else: shipping_method.price = taxed_price.net return available @staticmethod def resolve_available_payment_gateways(_: models.Checkout, _info): return [ gtw for gtw in get_extensions_manager().list_payment_gateways() ] @staticmethod def resolve_gift_cards(root: models.Checkout, _info): return root.gift_cards.all() @staticmethod def resolve_is_shipping_required(root: models.Checkout, _info): return root.is_shipping_required() @staticmethod @permission_required(OrderPermissions.MANAGE_ORDERS) def resolve_private_meta(root: models.Checkout, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.Checkout, _info): return resolve_meta(root, _info)
class Voucher(CountableDjangoObjectType): categories = gql_optimizer.field( PrefetchingConnectionField( Category, description="List of categories this voucher applies to."), model_field="categories", ) collections = gql_optimizer.field( PrefetchingConnectionField( Collection, description="List of collections this voucher applies to."), model_field="collections", ) products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products this voucher applies to."), model_field="products", ) countries = graphene.List( CountryDisplay, description="List of countries available for the shipping voucher.", ) translation = graphene.Field( VoucherTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description= "Returns translated Voucher fields for the given language code.", resolver=resolve_translation, ) class Meta: description = """ Vouchers allow giving discounts to particular customers on categories, collections or specific products. They can be used during checkout by providing valid voucher codes.""" only_fields = [ "apply_once_per_order", "code", "discount_value", "discount_value_type", "end_date", "id", "min_amount_spent", "name", "start_date", "type", "usage_limit", "used", ] interfaces = [relay.Node] model = models.Voucher @staticmethod def resolve_categories(root: models.Voucher, *_args, **_kwargs): return root.categories.all() @staticmethod def resolve_collections(root: models.Voucher, info, **_kwargs): return root.collections.visible_to_user(info.context.user) @staticmethod def resolve_products(root: models.Voucher, info, **_kwargs): return root.products.visible_to_user(info.context.user) @staticmethod def resolve_countries(root: models.Voucher, *_args, **_kwargs): return [ CountryDisplay(code=country.code, country=country.name) for country in root.countries ]
class Collection(CountableDjangoObjectType, MetadataObjectType): products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products in this collection."), prefetch_related=prefetch_products_collection_sorted, ) background_image = graphene.Field( Image, size=graphene.Int(description="Size of the image.")) translation = graphene.Field( CollectionTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description=("Returns translated Collection fields " "for the given language code."), resolver=resolve_translation, ) class Meta: description = "Represents a collection of products." only_fields = [ "description", "description_json", "id", "is_published", "name", "publication_date", "seo_description", "seo_title", "slug", ] interfaces = [relay.Node] model = models.Collection @staticmethod def resolve_background_image(root: models.Collection, info, size=None, **_kwargs): if root.background_image: return Image.get_adjusted( image=root.background_image, alt=root.background_image_alt, size=size, rendition_key_set="background_images", info=info, ) @staticmethod def resolve_products(root: models.Collection, info, **_kwargs): if hasattr(root, "prefetched_products"): return root.prefetched_products qs = root.products.collection_sorted(info.context.user) return gql_optimizer.query(qs, info) @classmethod def get_node(cls, info, id): if info.context: user = info.context.user qs = cls._meta.model.objects.visible_to_user(user) return cls.maybe_optimize(info, qs, id) return None @staticmethod @permission_required("product.manage_products") def resolve_private_meta(root, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info)
class Checkout(CountableDjangoObjectType): available_shipping_methods = graphene.List( ShippingMethod, required=True, description='Shipping methods that can be used with this order.') available_payment_gateways = graphene.List( PaymentGatewayEnum, description='List of available payment gateways.', required=True) email = graphene.String(description='Email of a customer', required=True) is_shipping_required = graphene.Boolean( description='Returns True, if checkout requires shipping.', required=True) lines = gql_optimizer.field(graphene.List( CheckoutLine, description=( 'A list of checkout lines, each containing information about ' 'an item in the checkout.')), model_field='lines') shipping_price = graphene.Field( TaxedMoney, description='The price of the shipping, with all the taxes included.') subtotal_price = graphene.Field( TaxedMoney, description=( 'The price of the checkout before shipping, with taxes included.')) total_price = graphene.Field( TaxedMoney, description=( 'The sum of the the checkout line prices, with all the taxes,' 'shipping costs, and discounts included.')) class Meta: only_fields = [ 'billing_address', 'created', 'discount_amount', 'discount_name', 'is_shipping_required', 'last_change', 'note', 'quantity', 'shipping_address', 'shipping_method', 'token', 'translated_discount_name', 'user', 'voucher_code' ] description = 'Checkout object' model = models.Cart interfaces = [graphene.relay.Node] filter_fields = ['token'] def resolve_total_price(self, info): taxes = get_taxes_for_address(self.shipping_address) return self.get_total(discounts=info.context.discounts, taxes=taxes) def resolve_subtotal_price(self, info): taxes = get_taxes_for_address(self.shipping_address) return self.get_subtotal(taxes=taxes) def resolve_shipping_price(self, info): taxes = get_taxes_for_address(self.shipping_address) return self.get_shipping_price(taxes=taxes) def resolve_lines(self, info): return self.lines.prefetch_related('variant') def resolve_available_shipping_methods(self, info): taxes = get_taxes_for_address(self.shipping_address) price = self.get_subtotal(taxes=taxes, discounts=info.context.discounts) return applicable_shipping_methods(self, price.gross.amount) def resolve_available_payment_gateways(self, info): return settings.CHECKOUT_PAYMENT_GATEWAYS.keys() def resolve_is_shipping_required(self, info): return self.is_shipping_required()
class ProductVariant(CountableDjangoObjectType): stock_quantity = graphene.Int( required=True, description='Quantity of a product available for sale.') price_override = graphene.Field( Money, description=dedent("""Override the base price of a product if necessary. A value of `null` indicates that the default product price is used.""")) price = graphene.Field(Money, description="Price of the product variant.") attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description='List of attributes assigned to this variant.') cost_price = graphene.Field( Money, description='Cost price of the variant.') margin = graphene.Int(description='Gross margin percentage value.') quantity_ordered = graphene.Int(description='Total quantity ordered.') revenue = graphene.Field( TaxedMoney, period=graphene.Argument(ReportingPeriod), description=dedent('''Total revenue generated by a variant in given period of time. Note: this field should be queried using `reportProductSales` query as it uses optimizations suitable for such calculations.''')) images = gql_optimizer.field( graphene.List( lambda: ProductImage, description='List of images for the product variant'), model_field='images') class Meta: description = dedent("""Represents a version of a product such as different size or color.""") exclude_fields = ['variant_images'] interfaces = [relay.Node] model = models.ProductVariant def resolve_stock_quantity(self, info): return self.quantity_available @gql_optimizer.resolver_hints( prefetch_related='product__product_type__variant_attributes__values') def resolve_attributes(self, info): attributes_qs = self.product.product_type.variant_attributes.all() return resolve_attribute_list(self.attributes, attributes_qs) def resolve_margin(self, info): return get_margin_for_variant(self) def resolve_price(self, info): return ( self.price_override if self.price_override is not None else self.product.price) @permission_required('product.manage_products') def resolve_price_override(self, info): return self.price_override def resolve_quantity_ordered(self, info): # This field is added through annotation when using the # `resolve_report_product_sales` resolver. return getattr(self, 'quantity_ordered', None) def resolve_revenue(self, info, period): start_date = reporting_period_to_date(period) return calculate_revenue_for_variant(self, start_date) def resolve_images(self, info): return self.images.all()
class OrderEventOrderLineObject(graphene.ObjectType): quantity = graphene.Int(description="The variant quantity.") order_line = gql_optimizer.field( graphene.Field(lambda: OrderLine, description="The order line.")) item_name = graphene.String(description="The variant name.")
class ProductVariant(CountableDjangoObjectType): quantity = graphene.Int( required=True, description="Quantity of a product in the store's possession, " "including the allocated stock that is waiting for shipment.", deprecation_reason= ("Use the stock field instead. This field will be removed after 2020-07-31." ), ) quantity_allocated = graphene.Int( required=False, description="Quantity allocated for orders", deprecation_reason= ("Use the stock field instead. This field will be removed after 2020-07-31." ), ) stock_quantity = graphene.Int( required=True, description="Quantity of a product available for sale.", deprecation_reason= ("Use the stock field instead. This field will be removed after 2020-07-31." ), ) price_override = graphene.Field( Money, description= ("Override the base price of a product if necessary. A value of `null` " "indicates that the default product price is used."), ) pricing = graphene.Field( VariantPricingInfo, description= ("Lists the storefront variant's pricing, the current price and discounts, " "only meant for displaying."), ) is_available = graphene.Boolean( description="Whether the variant is in stock and visible or not.", deprecation_reason= ("Use the stock field instead. This field will be removed after 2020-07-31." ), ) attributes = gql_optimizer.field( graphene.List( graphene.NonNull(SelectedAttribute), required=True, description="List of attributes assigned to this variant.", )) cost_price = graphene.Field(Money, description="Cost price of the variant.") margin = graphene.Int(description="Gross margin percentage value.") quantity_ordered = graphene.Int(description="Total quantity ordered.") revenue = graphene.Field( TaxedMoney, period=graphene.Argument(ReportingPeriod), description= ("Total revenue generated by a variant in given period of time. Note: this " "field should be queried using `reportProductSales` query as it uses " "optimizations suitable for such calculations."), ) images = gql_optimizer.field( graphene.List(lambda: ProductImage, description="List of images for the product variant."), model_field="images", ) translation = TranslationField(ProductVariantTranslation, type_name="product variant") digital_content = gql_optimizer.field( graphene.Field(DigitalContent, description="Digital content for the product variant."), model_field="digital_content", ) stocks = gql_optimizer.field( graphene.Field( graphene.List(Stock), description="Stocks for the product variant.", country_code=graphene.Argument( CountryCodeEnum, description="Two-letter ISO 3166-1 country code.", required=False, ), )) class Meta: description = ( "Represents a version of a product such as different size or color." ) only_fields = [ "id", "name", "product", "sku", "track_inventory", "weight" ] interfaces = [relay.Node, ObjectWithMetadata] model = models.ProductVariant @staticmethod def resolve_stocks(root: models.ProductVariant, info, country_code=None): if not country_code: return gql_optimizer.query( root.stocks.annotate_available_quantity().all(), info) return gql_optimizer.query( root.stocks.annotate_available_quantity().for_country( country_code).all(), info, ) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_digital_content(root: models.ProductVariant, *_args): return getattr(root, "digital_content", None) @staticmethod def resolve_stock_quantity(root: models.ProductVariant, info): return get_available_quantity_for_customer(root, info.context.country) @staticmethod @gql_optimizer.resolver_hints(prefetch_related=[ "attributes__values", "attributes__assignment__attribute" ]) def resolve_attributes(root: models.ProductVariant, info): return resolve_attribute_list(root, user=info.context.user) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_margin(root: models.ProductVariant, *_args): return get_margin_for_variant(root) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_cost_price(root: models.ProductVariant, *_args): return root.cost_price @staticmethod def resolve_price(root: models.ProductVariant, *_args): return root.base_price @staticmethod @gql_optimizer.resolver_hints(prefetch_related=("product", ), only=["price_override_amount", "currency"]) def resolve_pricing(root: models.ProductVariant, info): context = info.context availability = get_variant_availability( root, context.discounts, context.country, context.currency, plugins=context.plugins, ) return VariantPricingInfo(**asdict(availability)) @staticmethod def resolve_is_available(root: models.ProductVariant, info): country = info.context.country return is_variant_in_stock(root, country) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_price_override(root: models.ProductVariant, *_args): return root.price_override @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_quantity(root: models.ProductVariant, info): return get_available_quantity(root, info.context.country) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_quantity_ordered(root: models.ProductVariant, *_args): # This field is added through annotation when using the # `resolve_report_product_sales` resolver. return getattr(root, "quantity_ordered", None) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_quantity_allocated(root: models.ProductVariant, info): country = info.context.country return get_quantity_allocated(root, country) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_revenue(root: models.ProductVariant, *_args, period): start_date = reporting_period_to_date(period) return calculate_revenue_for_variant(root, start_date) @staticmethod def resolve_images(root: models.ProductVariant, *_args): return root.images.all() @classmethod def get_node(cls, info, id): user = info.context.user visible_products = models.Product.objects.visible_to_user( user).values_list("pk", flat=True) qs = cls._meta.model.objects.filter(product__id__in=visible_products) return cls.maybe_optimize(info, qs, id) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_private_meta(root: models.ProductVariant, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.ProductVariant, _info): return resolve_meta(root, _info) @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class OrderEvent(CountableDjangoObjectType): date = graphene.types.datetime.DateTime( description="Date when event happened at in ISO 8601 format.") type = OrderEventsEnum(description="Order event type.") user = graphene.Field( User, id=graphene.Argument(graphene.ID), description="User who performed the action.", ) message = graphene.String(description="Content of the event.") email = graphene.String(description="Email of the customer.") email_type = OrderEventsEmailsEnum( description="Type of an email sent to the customer.") amount = graphene.Float(description="Amount of money.") payment_id = graphene.String( description="The payment ID from the payment gateway.") payment_gateway = graphene.String( description="The payment gateway of the payment.") quantity = graphene.Int(description="Number of items.") composed_id = graphene.String( description="Composed ID of the Fulfillment.") order_number = graphene.String( description="User-friendly number of an order.") oversold_items = graphene.List(graphene.String, description="List of oversold lines names.") lines = graphene.List(OrderEventOrderLineObject, description="The concerned lines.") fulfilled_items = gql_optimizer.field( graphene.List(lambda: FulfillmentLine, description="The lines fulfilled.")) class Meta: description = "History log of the order." model = models.OrderEvent interfaces = [relay.Node] only_fields = ["id"] @staticmethod def resolve_email(root: models.OrderEvent, _info): return root.parameters.get("email", None) @staticmethod def resolve_email_type(root: models.OrderEvent, _info): return root.parameters.get("email_type", None) @staticmethod def resolve_amount(root: models.OrderEvent, _info): amount = root.parameters.get("amount", None) return float(amount) if amount else None @staticmethod def resolve_payment_id(root: models.OrderEvent, _info): return root.parameters.get("payment_id", None) @staticmethod def resolve_payment_gateway(root: models.OrderEvent, _info): return root.parameters.get("payment_gateway", None) @staticmethod def resolve_quantity(root: models.OrderEvent, _info): quantity = root.parameters.get("quantity", None) return int(quantity) if quantity else None @staticmethod def resolve_message(root: models.OrderEvent, _info): return root.parameters.get("message", None) @staticmethod def resolve_composed_id(root: models.OrderEvent, _info): return root.parameters.get("composed_id", None) @staticmethod def resolve_oversold_items(root: models.OrderEvent, _info): return root.parameters.get("oversold_items", None) @staticmethod def resolve_order_number(root: models.OrderEvent, _info): return root.order_id @staticmethod def resolve_lines(root: models.OrderEvent, _info): raw_lines = root.parameters.get("lines", None) if not raw_lines: return None line_pks = [] for entry in raw_lines: line_pks.append(entry.get("line_pk", None)) lines = models.OrderLine.objects.filter(pk__in=line_pks).all() results = [] for raw_line, line_pk in zip(raw_lines, line_pks): line_object = None for line in lines: if line.pk == line_pk: line_object = line break results.append( OrderEventOrderLineObject( quantity=raw_line["quantity"], order_line=line_object, item_name=raw_line["item"], )) return results @staticmethod def resolve_fulfilled_items(root: models.OrderEvent, _info): lines = root.parameters.get("fulfilled_items", None) return models.FulfillmentLine.objects.filter(pk__in=lines)
class ProductType(CountableDjangoObjectType): products = PrefetchingConnectionField( Product, description="List of products of this type.") tax_rate = TaxRateType(description="A type of tax rate.") tax_type = graphene.Field( TaxType, description="A type of tax. Assigned by enabled tax gateway") variant_attributes = graphene.List( Attribute, description="Variant attributes of that product type.") product_attributes = graphene.List( Attribute, description="Product attributes of that product type.") available_attributes = gql_optimizer.field( FilterInputConnectionField(Attribute, filter=AttributeFilterInput())) class Meta: description = ( "Represents a type of product. It defines what attributes are available to " "products of this type.") interfaces = [relay.Node, ObjectWithMetadata] model = models.ProductType only_fields = [ "has_variants", "id", "is_digital", "is_shipping_required", "name", "slug", "weight", "tax_type", ] @staticmethod def resolve_tax_type(root: models.ProductType, info): tax_data = info.context.plugins.get_tax_code_from_object_meta(root) return TaxType(tax_code=tax_data.code, description=tax_data.description) @staticmethod def resolve_tax_rate(root: models.ProductType, _info, **_kwargs): # FIXME this resolver should be dropped after we drop tax_rate from API if not hasattr(root, "meta"): return None return root.get_value_from_metadata("vatlayer.code") @staticmethod @gql_optimizer.resolver_hints( prefetch_related="product_attributes__attributeproduct") def resolve_product_attributes(root: models.ProductType, *_args, **_kwargs): return root.product_attributes.product_attributes_sorted().all() @staticmethod @gql_optimizer.resolver_hints( prefetch_related="variant_attributes__attributevariant") def resolve_variant_attributes(root: models.ProductType, *_args, **_kwargs): return root.variant_attributes.variant_attributes_sorted().all() @staticmethod def resolve_products(root: models.ProductType, info, **_kwargs): if hasattr(root, "prefetched_products"): return root.prefetched_products # type: ignore qs = root.products.visible_to_user(info.context.user) return gql_optimizer.query(qs, info) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_available_attributes(root: models.ProductType, info, **kwargs): qs = models.Attribute.objects.get_unassigned_attributes(root.pk) return resolve_attributes(info, qs=qs, **kwargs) @staticmethod @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_private_meta(root: models.ProductType, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.ProductType, _info): return resolve_meta(root, _info) @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class Voucher(CountableDjangoObjectType): categories = gql_optimizer.field( PrefetchingConnectionField( Category, description="List of categories this voucher applies to."), model_field="categories", ) collections = gql_optimizer.field( PrefetchingConnectionField( Collection, description="List of collections this voucher applies to."), model_field="collections", ) products = gql_optimizer.field( PrefetchingConnectionField( Product, description="List of products this voucher applies to."), model_field="products", ) countries = graphene.List( types.CountryDisplay, description="List of countries available for the shipping voucher.", ) translation = graphene.Field( VoucherTranslation, language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", required=True, ), description= "Returns translated Voucher fields for the given language code.", resolver=resolve_translation, ) discount_value_type = DiscountValueTypeEnum( description= "Determines a type of discount for voucher - value or percentage", required=True, ) type = VoucherTypeEnum(description="Determines a type of voucher.", required=True) min_amount_spent = graphene.Field( types.Money, deprecation_reason=("DEPRECATED: Will be removed in Saleor 2.10, " "use the minSpent field instead."), ) class Meta: description = ( "Vouchers allow giving discounts to particular customers on categories, " "collections or specific products. They can be used during checkout by " "providing valid voucher codes.") only_fields = [ "apply_once_per_order", "apply_once_per_customer", "code", "discount_value", "discount_value_type", "end_date", "id", "min_spent", "min_checkout_items_quantity", "name", "start_date", "type", "usage_limit", "used", ] interfaces = [relay.Node] model = models.Voucher @staticmethod def resolve_categories(root: models.Voucher, *_args, **_kwargs): return root.categories.all() @staticmethod def resolve_collections(root: models.Voucher, info, **_kwargs): return root.collections.visible_to_user(info.context.user) @staticmethod def resolve_products(root: models.Voucher, info, **_kwargs): return root.products.visible_to_user(info.context.user) @staticmethod def resolve_countries(root: models.Voucher, *_args, **_kwargs): return [ types.CountryDisplay(code=country.code, country=country.name) for country in root.countries ] @staticmethod def resolve_min_amount_spent(root: models.Voucher, *_args, **_kwargs): return root.min_spent
class Product(CountableDjangoObjectType): url = graphene.String(description='The storefront URL for the product.', required=True) thumbnail_url = graphene.String( description='The URL of a main thumbnail for a product.', size=graphene.Argument(graphene.Int, description='Size of thumbnail'), deprecation_reason=dedent("""thumbnailUrl is deprecated, use thumbnail instead""")) thumbnail = graphene.Field(Image, description='The main thumbnail for a product.', size=graphene.Argument( graphene.Int, description='Size of thumbnail')) availability = graphene.Field( ProductAvailability, description=dedent("""Informs about product's availability in the storefront, current price and discounts.""")) price = graphene.Field( Money, description=dedent("""The product's base price (without any discounts applied).""")) tax_rate = TaxRateType(description='A type of tax rate.') attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description='List of attributes assigned to this product.') purchase_cost = graphene.Field(MoneyRange) margin = graphene.Field(Margin) image_by_id = graphene.Field( lambda: ProductImage, id=graphene.Argument(graphene.ID, description='ID of a product image.'), description='Get a single product image by ID') variants = gql_optimizer.field(graphene.List( ProductVariant, description='List of variants for the product'), model_field='variants') images = gql_optimizer.field(graphene.List( lambda: ProductImage, description='List of images for the product'), model_field='images') collections = gql_optimizer.field(graphene.List( lambda: Collection, description='List of collections for the product'), model_field='collections') available_on = graphene.Date(deprecation_reason=( 'availableOn is deprecated, use publicationDate instead')) translation = graphene.Field( ProductTranslation, language_code=graphene.Argument( LanguageCodeEnum, description='A language code to return the translation for.', required=True), description=( 'Returns translated Product fields for the given language code.'), resolver=resolve_translation) class Meta: description = dedent("""Represents an individual item for sale in the storefront.""") interfaces = [relay.Node] model = models.Product only_fields = [ 'category', 'charge_taxes', 'description', 'description_json', 'id', 'is_published', 'name', 'product_type', 'publication_date', 'seo_description', 'seo_title', 'updated_at', 'weight' ] @gql_optimizer.resolver_hints(prefetch_related='images') def resolve_thumbnail_url(self, info, *, size=None): if not size: size = 255 url = get_product_image_thumbnail(self.get_first_image(), size, method='thumbnail') return info.context.build_absolute_uri(url) @gql_optimizer.resolver_hints(prefetch_related='images') def resolve_thumbnail(self, info, *, size=None): image = self.get_first_image() if not size: size = 255 url = get_product_image_thumbnail(image, size, method='thumbnail') url = info.context.build_absolute_uri(url) alt = image.alt if image else None return Image(alt=alt, url=url) def resolve_url(self, *_args): return self.get_absolute_url() @gql_optimizer.resolver_hints( prefetch_related=('variants', 'collections'), only=['publication_date', 'charge_taxes', 'price', 'tax_rate']) def resolve_availability(self, info): context = info.context availability = get_availability(self, context.discounts, context.taxes, context.currency) return ProductAvailability(**availability._asdict()) @gql_optimizer.resolver_hints( prefetch_related='product_type__product_attributes__values') def resolve_attributes(self, *_args): attributes_qs = self.product_type.product_attributes.all() return resolve_attribute_list(self.attributes, attributes_qs) @permission_required('product.manage_products') def resolve_purchase_cost(self, *_args): purchase_cost, _ = get_product_costs_data(self) return purchase_cost @permission_required('product.manage_products') def resolve_margin(self, *_args): _, margin = get_product_costs_data(self) return Margin(margin[0], margin[1]) def resolve_image_by_id(self, info, id): pk = get_database_id(info, id, ProductImage) try: return self.images.get(pk=pk) except models.ProductImage.DoesNotExist: raise GraphQLError('Product image not found.') @gql_optimizer.resolver_hints(model_field='images') def resolve_images(self, *_args, **_kwargs): return self.images.all() def resolve_variants(self, *_args, **_kwargs): return self.variants.all() def resolve_collections(self, *_args): return self.collections.all() def resolve_available_on(self, *_args): return self.publication_date @classmethod def get_node(cls, info, id): if info.context: user = info.context.user try: return cls._meta.model.objects.visible_to_user(user).get(pk=id) except cls._meta.model.DoesNotExist: return None return None
class Query(graphene.ObjectType): plan = gql_optimizer.field( graphene.Field(PlanNode, id=graphene.ID(required=True))) all_plans = graphene.List(PlanNode) action = graphene.Field(ActionNode, id=graphene.ID(), identifier=graphene.ID(), plan=graphene.ID()) indicator = graphene.Field(IndicatorNode, id=graphene.ID(), identifier=graphene.ID(), plan=graphene.ID()) person = graphene.Field(PersonNode, id=graphene.ID(required=True)) static_page = graphene.Field(StaticPageNode, plan=graphene.ID(), slug=graphene.ID()) plan_actions = graphene.List(ActionNode, plan=graphene.ID(required=True), first=graphene.Int(), order_by=graphene.String()) plan_categories = graphene.List(CategoryNode, plan=graphene.ID(required=True)) plan_organizations = graphene.List(OrganizationNode, plan=graphene.ID(required=True)) plan_indicators = graphene.List( IndicatorNode, plan=graphene.ID(required=True), first=graphene.Int(), order_by=graphene.String(), has_data=graphene.Boolean(), has_goals=graphene.Boolean(), ) def resolve_plan(self, info, **kwargs): qs = Plan.objects.all() try: plan = gql_optimizer.query(qs, info).get(identifier=kwargs['id']) except Plan.DoesNotExist: return None return plan def resolve_all_plans(self, info): return Plan.objects.all() def resolve_plan_actions(self, info, plan, first=None, order_by=None, **kwargs): qs = Action.objects.all() qs = qs.filter(plan__identifier=plan) qs = order_queryset(qs, ActionNode, order_by) if first is not None: qs = qs[0:first] return gql_optimizer.query(qs, info) def resolve_plan_categories(self, info, **kwargs): qs = Category.objects.all() plan = kwargs.get('plan') if plan is not None: qs = qs.filter(type__plan__identifier=plan) return gql_optimizer.query(qs, info) def resolve_plan_organizations(self, info, **kwargs): qs = Organization.objects.all() plan = kwargs.get('plan') if plan is not None: qs = qs.filter( responsible_actions__action__plan__identifier=plan).distinct() return gql_optimizer.query(qs, info) def resolve_plan_indicators(self, info, plan, first=None, order_by=None, has_data=None, has_goals=None, **kwargs): qs = Indicator.objects.all() qs = qs.filter(levels__plan__identifier=plan).distinct() if has_data is not None: qs = qs.filter(latest_value__isnull=not has_data) if has_goals is not None: qs = qs.filter(goals__plan__identifier=plan).distinct() qs = order_queryset(qs, IndicatorNode, order_by) if first is not None: qs = qs[0:first] return gql_optimizer.query(qs, info) def resolve_action(self, info, **kwargs): obj_id = kwargs.get('id') identifier = kwargs.get('identifier') plan = kwargs.get('plan') if identifier and not plan: raise Exception( "You must supply the 'plan' argument when using 'identifier'") qs = Action.objects.all() if obj_id: qs = qs.filter(id=obj_id) if identifier: qs = qs.filter(identifier=identifier, plan__identifier=plan) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except Action.DoesNotExist: return None return obj def resolve_person(self, info, **kwargs): qs = Person.objects.all() obj_id = kwargs.get('id') qs = qs.filter(id=obj_id) try: obj = qs.get() except Person.DoesNotExist: return None return obj def resolve_indicator(self, info, **kwargs): obj_id = kwargs.get('id') identifier = kwargs.get('identifier') plan = kwargs.get('plan') if not identifier and not obj_id: raise Exception("You must supply either 'id' or 'identifier'") qs = Indicator.objects.all() if obj_id: qs = qs.filter(id=obj_id) if plan: qs = qs.filter(levels__plan__identifier=plan).distinct() if identifier: qs = qs.filter(identifier=identifier) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except Indicator.DoesNotExist: return None return obj def resolve_static_page(self, info, **kwargs): slug = kwargs.get('slug') plan = kwargs.get('plan') if not slug or not plan: raise Exception("You must supply both 'slug' and 'plan'") qs = StaticPage.objects.all() qs = qs.filter(slug=slug, plan__identifier=plan) qs = gql_optimizer.query(qs, info) try: obj = qs.get() except StaticPage.DoesNotExist: return None return obj
class Order(MetadataObjectType, CountableDjangoObjectType): fulfillments = gql_optimizer.field( graphene.List(Fulfillment, required=True, description="List of shipments for the order."), model_field="fulfillments", ) lines = gql_optimizer.field( graphene.List(lambda: OrderLine, required=True, description="List of order lines."), model_field="lines", ) actions = graphene.List( OrderAction, description= ("List of actions that can be performed in the current state of an order." ), required=True, ) available_shipping_methods = graphene.List( ShippingMethod, required=False, description="Shipping methods that can be used with this order.", ) number = graphene.String(description="User-friendly number of an order.") is_paid = graphene.Boolean( description="Informs if an order is fully paid.") payment_status = PaymentChargeStatusEnum( description="Internal payment status.") payment_status_display = graphene.String( description="User-friendly payment status.") payments = gql_optimizer.field( graphene.List(Payment, description="List of payments for the order."), model_field="payments", ) total = graphene.Field(TaxedMoney, description="Total amount of the order.") shipping_price = graphene.Field(TaxedMoney, description="Total price of shipping.") subtotal = graphene.Field( TaxedMoney, description="The sum of line prices not including shipping.") gift_cards = gql_optimizer.field( graphene.List(GiftCard, description="List of user gift cards."), model_field="gift_cards", ) status_display = graphene.String(description="User-friendly order status.") can_finalize = graphene.Boolean( description=("Informs whether a draft order can be finalized" "(turned into a regular order)."), required=True, ) total_authorized = graphene.Field( Money, description="Amount authorized for the order.") total_captured = graphene.Field(Money, description="Amount captured by payment.") events = gql_optimizer.field( graphene.List(OrderEvent, description="List of events associated with the order."), model_field="events", ) total_balance = graphene.Field( Money, description= "The difference between the paid and the order total amount.", required=True, ) user_email = graphene.String(required=False, description="Email address of the customer.") is_shipping_required = graphene.Boolean( description="Returns True, if order requires shipping.", required=True) class Meta: description = "Represents an order in the shop." interfaces = [relay.Node] model = models.Order only_fields = [ "billing_address", "created", "customer_note", "discount", "discount_name", "display_gross_prices", "gift_cards", "id", "language_code", "shipping_address", "shipping_method", "shipping_method_name", "shipping_price", "status", "token", "tracking_client_id", "translated_discount_name", "user", "voucher", "weight", ] @staticmethod def resolve_shipping_price(root: models.Order, _info): return root.shipping_price @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments__transactions") def resolve_actions(root: models.Order, _info): actions = [] payment = root.get_last_payment() if root.can_capture(payment): actions.append(OrderAction.CAPTURE) if root.can_mark_as_paid(): actions.append(OrderAction.MARK_AS_PAID) if root.can_refund(payment): actions.append(OrderAction.REFUND) if root.can_void(payment): actions.append(OrderAction.VOID) return actions @staticmethod def resolve_subtotal(root: models.Order, _info): return root.get_subtotal() @staticmethod def resolve_total(root: models.Order, _info): return root.total @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments__transactions") def resolve_total_authorized(root: models.Order, _info): # FIXME adjust to multiple payments in the future return root.total_authorized @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments") def resolve_total_captured(root: models.Order, _info): # FIXME adjust to multiple payments in the future return root.total_captured @staticmethod def resolve_total_balance(root: models.Order, _info): return root.total_balance @staticmethod def resolve_fulfillments(root: models.Order, info): user = info.context.user if user.is_staff: qs = root.fulfillments.all() else: qs = root.fulfillments.exclude(status=FulfillmentStatus.CANCELED) return qs.order_by("pk") @staticmethod def resolve_lines(root: models.Order, _info): return root.lines.all().order_by("pk") @staticmethod def resolve_events(root: models.Order, _info): return root.events.all().order_by("pk") @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments") def resolve_is_paid(root: models.Order, _info): return root.is_fully_paid() @staticmethod def resolve_number(root: models.Order, _info): return str(root.pk) @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments") def resolve_payment_status(root: models.Order, _info): return root.get_payment_status() @staticmethod @gql_optimizer.resolver_hints(prefetch_related="payments") def resolve_payment_status_display(root: models.Order, _info): return root.get_payment_status_display() @staticmethod def resolve_payments(root: models.Order, _info): return root.payments.all() @staticmethod def resolve_status_display(root: models.Order, _info): return root.get_status_display() @staticmethod def resolve_can_finalize(root: models.Order, _info): try: validate_draft_order(root) except ValidationError: return False return True @staticmethod @gql_optimizer.resolver_hints(select_related="user") def resolve_user_email(root: models.Order, _info): return root.get_customer_email() @staticmethod def resolve_available_shipping_methods(root: models.Order, _info): available = get_valid_shipping_methods_for_order(root) if available is None: return [] manager = get_extensions_manager() display_gross = display_gross_prices() for shipping_method in available: taxed_price = manager.apply_taxes_to_shipping( shipping_method.price, root.shipping_address) if display_gross: shipping_method.price = taxed_price.gross else: shipping_method.price = taxed_price.net return available @staticmethod def resolve_is_shipping_required(root: models.Order, _info): return root.is_shipping_required() @staticmethod def resolve_gift_cards(root: models.Order, _info): return root.gift_cards.all() @staticmethod @permission_required(OrderPermissions.MANAGE_ORDERS) def resolve_private_meta(root: models.Order, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.Order, _info): return resolve_meta(root, _info)
class User(CountableDjangoObjectType): addresses = gql_optimizer.field( graphene.List(Address, description="List of all user's addresses."), model_field="addresses", ) checkout = graphene.Field( Checkout, description="Returns the last open checkout of this user.") gift_cards = gql_optimizer.field( PrefetchingConnectionField( "saleor.graphql.giftcard.types.GiftCard", description="List of the user gift cards.", ), model_field="gift_cards", ) note = graphene.String(description="A note about the customer.") orders = gql_optimizer.field( PrefetchingConnectionField("saleor.graphql.order.types.Order", description="List of user's orders."), model_field="orders", ) # deprecated, to remove in #5389 permissions = gql_optimizer.field( graphene.List( Permission, description="List of user's permissions.", deprecation_reason=("Will be removed in Saleor 2.11." "Use the `userPermissions` instead."), ), model_field="user_permissions", ) user_permissions = gql_optimizer.field( graphene.List(UserPermission, description="List of user's permissions."), model_field="user_permissions", ) permission_groups = gql_optimizer.field( graphene.List( "saleor.graphql.account.types.Group", description="List of user's permission groups.", ), model_field="groups", ) editable_groups = graphene.List( "saleor.graphql.account.types.Group", description="List of user's permission groups which user can manage.", ) avatar = graphene.Field( Image, size=graphene.Int(description="Size of the avatar.")) events = gql_optimizer.field( graphene.List(CustomerEvent, description="List of events associated with the user."), model_field="events", ) stored_payment_sources = graphene.List( "saleor.graphql.payment.types.PaymentSource", description="List of stored payment sources.", ) class Meta: description = "Represents user data." interfaces = [relay.Node, ObjectWithMetadata] model = get_user_model() only_fields = [ "date_joined", "default_billing_address", "default_shipping_address", "email", "first_name", "id", "is_active", "is_staff", "last_login", "last_name", "note", ] @staticmethod def resolve_addresses(root: models.User, _info, **_kwargs): return root.addresses.annotate_default(root).all() @staticmethod def resolve_checkout(root: models.User, _info, **_kwargs): return get_user_checkout(root)[0] @staticmethod def resolve_gift_cards(root: models.User, info, **_kwargs): return root.gift_cards.all() @staticmethod def resolve_permissions(root: models.User, _info, **_kwargs): # deprecated, to remove in #5389 from .resolvers import resolve_permissions return resolve_permissions(root) @staticmethod def resolve_user_permissions(root: models.User, _info, **_kwargs): from .resolvers import resolve_permissions return resolve_permissions(root) @staticmethod def resolve_permission_groups(root: models.User, _info, **_kwargs): return root.groups.all() @staticmethod def resolve_editable_groups(root: models.User, _info, **_kwargs): return get_groups_which_user_can_manage(root) @staticmethod @one_of_permissions_required( [AccountPermissions.MANAGE_USERS, AccountPermissions.MANAGE_STAFF]) def resolve_note(root: models.User, info): return root.note @staticmethod @one_of_permissions_required( [AccountPermissions.MANAGE_USERS, AccountPermissions.MANAGE_STAFF]) def resolve_events(root: models.User, info): return root.events.all() @staticmethod def resolve_orders(root: models.User, info, **_kwargs): viewer = info.context.user if viewer.has_perm(OrderPermissions.MANAGE_ORDERS): return root.orders.all() return root.orders.confirmed() @staticmethod def resolve_avatar(root: models.User, info, size=None, **_kwargs): if root.avatar: return Image.get_adjusted( image=root.avatar, alt=None, size=size, rendition_key_set="user_avatars", info=info, ) @staticmethod def resolve_stored_payment_sources(root: models.User, info): from .resolvers import resolve_payment_sources if root == info.context.user: return resolve_payment_sources(root) raise PermissionDenied() @staticmethod @one_of_permissions_required( [AccountPermissions.MANAGE_USERS, AccountPermissions.MANAGE_STAFF]) def resolve_private_meta(root: models.User, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root: models.User, _info): return resolve_meta(root, _info) @staticmethod def resolve_wishlist(root: models.User, info, **_kwargs): return resolve_wishlist_items_from_user(root) @staticmethod def __resolve_reference(root, _info, **_kwargs): if root.id is not None: return graphene.Node.get_node_from_global_id(_info, root.id) return get_user_model().objects.get(email=root.email)