class Arguments: backfillParams = graphene.NonNull(GraphenePartitionBackfillParams)
class ProductWithoutVariantError(Error): products = graphene.List( graphene.NonNull(graphene.ID), description="List of products IDs which causes the error.", )
class BaseMutation(graphene.Mutation): errors = graphene.List( graphene.NonNull(Error), description='List of errors that occurred executing the mutation.') class Meta: abstract = True @classmethod def __init_subclass_with_meta__(cls, description=None, **options): if not description: raise ImproperlyConfigured('No description provided in Meta') description = dedent(description) super().__init_subclass_with_meta__(description=description, **options) @classmethod def _update_mutation_arguments_and_fields(cls, arguments, fields): cls._meta.arguments.update(arguments) cls._meta.fields.update(fields) @classmethod def add_error(cls, errors, field, message): """Add a mutation user error. `errors` is the list of errors that happened during the execution of the mutation. `field` is the name of an input field the error is related to. `None` value is allowed and it indicates that the error is general and is not related to any of the input fields. `message` is the actual error message to be returned in the response. As a result of this method, the `errors` list is updated with an Error object to be returned as mutation result. """ field = snake_to_camel_case(field) errors.append(Error(field=field, message=message)) @classmethod def get_node_or_error(cls, info, global_id, errors, field, only_type=None): if not global_id: return None node = None try: node = graphene.Node.get_node_from_global_id( info, global_id, only_type) except (AssertionError, GraphQLError) as e: cls.add_error(errors, field, str(e)) else: if node is None: message = "Couldn't resolve to a node: %s" % global_id cls.add_error(errors, field, message) return node @classmethod def get_nodes_or_error(cls, ids, errors, field, only_type=None): instances = None try: instances = get_nodes(ids, only_type) except GraphQLError as e: cls.add_error(field=field, message=str(e), errors=errors) return instances @classmethod def clean_instance(cls, instance, errors): """Clean the instance that was created using the input data. Once a instance is created, this method runs `full_clean()` to perform model fields' validation. Returns errors ready to be returned by the GraphQL response (if any occurred). """ try: instance.full_clean() except ValidationError as validation_errors: message_dict = validation_errors.message_dict for field in message_dict: if hasattr(cls._meta, 'exclude') and field in cls._meta.exclude: continue for message in message_dict[field]: field = snake_to_camel_case(field) cls.add_error(errors, field, message) return errors @classmethod def construct_instance(cls, instance, cleaned_data): """Fill instance fields with cleaned data. The `instance` argument is either an empty instance of a already existing one which was fetched from the database. `cleaned_data` is data to be set in instance fields. Returns `instance` with filled fields, but not saved to the database. """ from django.db import models opts = instance._meta for f in opts.fields: if any([ not f.editable, isinstance(f, models.AutoField), f.name not in cleaned_data ]): continue data = cleaned_data[f.name] if data is None: # We want to reset the file field value when None was passed # in the input, but `FileField.save_form_data` ignores None # values. In that case we manually pass False which clears # the file. if isinstance(f, FileField): data = False if not f.null: data = f._get_default() f.save_form_data(instance, data) return instance
class Shop(graphene.ObjectType): available_payment_gateways = graphene.List( graphene.NonNull(PaymentGateway), currency=graphene.Argument( graphene.String, description="A currency for which gateways will be returned.", required=False, ), description="List of available payment gateways.", required=True, ) geolocalization = graphene.Field( Geolocalization, description="Customer's geolocalization data." ) authorization_keys = graphene.List( AuthorizationKey, description=( "List of configured authorization keys. Authorization keys are used to " "enable third-party OAuth authorization (currently Facebook or Google)." ), required=True, ) countries = graphene.List( graphene.NonNull(CountryDisplay), language_code=graphene.Argument( LanguageCodeEnum, description="A language code to return the translation for.", ), description="List of countries available in the shop.", required=True, ) currencies = graphene.List( graphene.String, description="List of available currencies.", required=True ) default_currency = graphene.String( description="Shop's default currency.", required=True ) default_country = graphene.Field( CountryDisplay, description="Shop's default country." ) default_mail_sender_name = graphene.String( description="Default shop's email sender's name." ) default_mail_sender_address = graphene.String( description="Default shop's email sender's address." ) description = graphene.String(description="Shop's description.") domain = graphene.Field(Domain, required=True, description="Shop's domain data.") homepage_collection = graphene.Field( Collection, description="Collection displayed on homepage." ) languages = graphene.List( LanguageDisplay, description="List of the shops's supported languages.", required=True, ) name = graphene.String(description="Shop's name.", required=True) navigation = graphene.Field(Navigation, description="Shop's navigation.") permissions = graphene.List( Permission, description="List of available permissions.", required=True ) phone_prefixes = graphene.List( graphene.String, description="List of possible phone prefixes.", required=True ) header_text = graphene.String(description="Header text.") include_taxes_in_prices = graphene.Boolean( description="Include taxes in prices.", required=True ) display_gross_prices = graphene.Boolean( description="Display prices with tax in store.", required=True ) charge_taxes_on_shipping = graphene.Boolean( description="Charge taxes on shipping.", required=True ) track_inventory_by_default = graphene.Boolean( description="Enable inventory tracking." ) default_weight_unit = WeightUnitsEnum(description="Default weight unit.") translation = TranslationField(ShopTranslation, type_name="shop", resolver=None) automatic_fulfillment_digital_products = graphene.Boolean( description="Enable automatic fulfillment for all digital products." ) default_digital_max_downloads = graphene.Int( description="Default number of max downloads per digital content URL." ) default_digital_url_valid_days = graphene.Int( description="Default number of days which digital content URL will be valid." ) company_address = graphene.Field( Address, description="Company address.", required=False ) customer_set_password_url = graphene.String( description="URL of a view where customers can set their password.", required=False, ) staff_notification_recipients = graphene.List( StaffNotificationRecipient, description="List of staff notification recipients.", required=False, ) class Meta: description = ( "Represents a shop resource containing general shop data and configuration." ) @staticmethod def resolve_available_payment_gateways(_, _info, currency: Optional[str] = None): return get_plugins_manager().list_payment_gateways(currency=currency) @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_authorization_keys(_, _info): return site_models.AuthorizationKey.objects.all() @staticmethod def resolve_countries(_, _info, language_code=None): taxes = {vat.country_code: vat for vat in VAT.objects.all()} with translation.override(language_code): return [ CountryDisplay( code=country[0], country=country[1], vat=taxes.get(country[0]) ) for country in filter(lambda c: c[0] == "MN" or c[0] == "GB", countries) ] @staticmethod def resolve_currencies(_, _info): return settings.AVAILABLE_CURRENCIES @staticmethod def resolve_domain(_, info): site = info.context.site return Domain( host=site.domain, ssl_enabled=settings.ENABLE_SSL, url=info.context.build_absolute_uri("/"), ) @staticmethod def resolve_geolocalization(_, info): client_ip = get_client_ip(info.context) country = get_country_by_ip(client_ip) if country: return Geolocalization( country=CountryDisplay(code=country.code, country=country.name) ) return Geolocalization(country=None) @staticmethod def resolve_default_currency(_, _info): return settings.DEFAULT_CURRENCY @staticmethod def resolve_description(_, info): return info.context.site.settings.description @staticmethod def resolve_homepage_collection(_, info): collection_pk = info.context.site.settings.homepage_collection_id qs = product_models.Collection.objects.all() return qs.filter(pk=collection_pk).first() @staticmethod def resolve_languages(_, _info): return [ LanguageDisplay( code=LanguageCodeEnum[str_to_enum(language[0])], language=language[1] ) for language in settings.LANGUAGES ] @staticmethod def resolve_name(_, info): return info.context.site.name @staticmethod def resolve_navigation(_, info): site_settings = info.context.site.settings main = ( MenuByIdLoader(info.context).load(site_settings.top_menu_id) if site_settings.top_menu_id else None ) secondary = ( MenuByIdLoader(info.context).load(site_settings.bottom_menu_id) if site_settings.bottom_menu_id else None ) return Navigation(main=main, secondary=secondary) @staticmethod def resolve_permissions(_, _info): permissions = get_permissions() return format_permissions_for_display(permissions) @staticmethod def resolve_phone_prefixes(_, _info): return list(COUNTRY_CODE_TO_REGION_CODE.keys()) @staticmethod def resolve_header_text(_, info): return info.context.site.settings.header_text @staticmethod def resolve_include_taxes_in_prices(_, info): return info.context.site.settings.include_taxes_in_prices @staticmethod def resolve_display_gross_prices(_, info): return info.context.site.settings.display_gross_prices @staticmethod def resolve_charge_taxes_on_shipping(_, info): return info.context.site.settings.charge_taxes_on_shipping @staticmethod def resolve_track_inventory_by_default(_, info): return info.context.site.settings.track_inventory_by_default @staticmethod def resolve_default_weight_unit(_, info): return info.context.site.settings.default_weight_unit @staticmethod def resolve_default_country(_, _info): default_country_code = settings.DEFAULT_COUNTRY default_country_name = countries.countries.get(default_country_code) if default_country_name: vat = VAT.objects.filter(country_code=default_country_code).first() default_country = CountryDisplay( code=default_country_code, country=default_country_name, vat=vat ) else: default_country = None return default_country @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_default_mail_sender_name(_, info): return info.context.site.settings.default_mail_sender_name @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_default_mail_sender_address(_, info): return info.context.site.settings.default_mail_sender_address @staticmethod def resolve_company_address(_, info): return info.context.site.settings.company_address @staticmethod def resolve_customer_set_password_url(_, info): return info.context.site.settings.customer_set_password_url @staticmethod def resolve_translation(_, info, language_code): return resolve_translation(info.context.site.settings, info, language_code) @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_automatic_fulfillment_digital_products(_, info): site_settings = info.context.site.settings return site_settings.automatic_fulfillment_digital_products @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_default_digital_max_downloads(_, info): return info.context.site.settings.default_digital_max_downloads @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_default_digital_url_valid_days(_, info): return info.context.site.settings.default_digital_url_valid_days @staticmethod @permission_required(SitePermissions.MANAGE_SETTINGS) def resolve_staff_notification_recipients(_, info): return account_models.StaffNotificationRecipient.objects.all()
class LatestTextMessagesResult(graphene.ObjectType): messages = graphene.List(graphene.NonNull(LatestTextMessage), required=True) has_next_page = graphene.Boolean(required=True)
class Arguments: email = graphene.NonNull(Email) password = graphene.NonNull(graphene.String)
class GraphenePartitionRunConfig(graphene.ObjectType): yaml = graphene.NonNull(graphene.String) class Meta: name = "PartitionRunConfig"
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 = graphene.List( ProductVariant, description="List of variants for the product." ) images = graphene.List( lambda: ProductImage, description="List of images for the product." ) collections = graphene.List( lambda: Collection, description="List of collections for the product." ) 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_category(root: models.Product, info): category_id = root.category_id if category_id is None: return None return CategoryByIdLoader(info.context).load(category_id) @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 def resolve_thumbnail(root: models.Product, info, *, size=255): def return_first_thumbnail(images): image = images[0] if images else None 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 return ( ImagesByProductIdLoader(info.context) .load(root.id) .then(return_first_thumbnail) ) @staticmethod def resolve_url(root: models.Product, *_args): return "" @staticmethod def resolve_pricing(root: models.Product, info): context = info.context variants = ProductVariantsByProductIdLoader(context).load(root.id) collections = CollectionsByProductIdLoader(context).load(root.id) def calculate_pricing_info(discounts): def calculate_pricing_with_variants(variants): def calculate_pricing_with_collections(collections): availability = get_product_availability( product=root, variants=variants, collections=collections, discounts=discounts, country=context.country, local_currency=context.currency, plugins=context.plugins, ) return ProductPricingInfo(**asdict(availability)) return collections.then(calculate_pricing_with_collections) return variants.then(calculate_pricing_with_variants) return ( DiscountsByDateTimeLoader(context) .load(info.context.request_time) .then(calculate_pricing_info) ) @staticmethod 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 def resolve_price(root: models.Product, info): context = info.context def calculate_price(discounts): price_range = root.get_price_range(discounts) price = info.context.plugins.apply_taxes_to_product( root, price_range.start, info.context.country ) return price.net return ( DiscountsByDateTimeLoader(context) .load(info.context.request_time) .then(calculate_price) ) @staticmethod def resolve_attributes(root: models.Product, info): return SelectedAttributesByProductIdLoader(info.context).load(root.id) @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 def resolve_images(root: models.Product, info, **_kwargs): return ImagesByProductIdLoader(info.context).load(root.id) @staticmethod def resolve_variants(root: models.Product, info, **_kwargs): return ProductVariantsByProductIdLoader(info.context).load(root.id) @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 qs.filter(pk=pk).first() 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 Input: username = graphene.NonNull(graphene.String) password = graphene.NonNull(graphene.String)
class Token(graphene.ObjectType): """ self = CommunityDB Object """ class Meta: description = "Representds an ERC-20 token contract in Band ecosystem, including Band native token and community tokens." address = graphene.String(required=True, description="This token's ERC-20 address.") community = graphene.Field( lambda: community_module.Community, description= "The community of this token, in the case of community token.", ) balances = graphene.List( lambda: graphene.NonNull(user_balance_module.UserBalance), filtered_by=graphene.Argument( lambda: user_balance_module.UserBalanceFilters), required=True, description="The list of user balances of this token.", ) transfer_history = graphene.List( lambda: transfer_module.Transfer, filtered_by=graphene.Argument(lambda: transfer_module.TransferFilters), required=True, description="The list of transfers of this token.", ) name = graphene.String(required=True, description="The name of this token.") symbol = graphene.String(required=True, description="The symbol of this token.") def resolve_address(self, info): contract = (db.session.query(Contract).filter( Contract.contract_type == "TOKEN").filter( Contract.community_id == self.id).one()) return contract.address def resolve_community(self, info): if self.id == 1: return None return self def resolve_balances(self, info, filtered_by={}): if "users" in filtered_by: return [(address, self) for address in filtered_by["users"]] addresses = (db.session.query(func.distinct( TransferDB.receiver)).filter( TransferDB.receiver != "0x0000000000000000000000000000000000000000").filter( TransferDB.community_id == self.id).all()) return [(address[0], self) for address in addresses] def resolve_transfer_history(self, info, filtered_by={}): return get_transfer_history(**filtered_by, tokens=[self]) def resolve_name(self, info): return self.name def resolve_symbol(self, info): return self.symbol
class ProductVariant(CountableDjangoObjectType): 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." ), ) 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 quantityAvailable 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 = 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 = graphene.List( lambda: ProductImage, description="List of images for the product variant." ) translation = TranslationField( ProductVariantTranslation, type_name="product variant" ) digital_content = graphene.Field( DigitalContent, description="Digital content for the product variant." ) stocks = 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, ), ) quantity_available = graphene.Int( required=True, description="Quantity of a product available for sale in one checkout.", country_code=graphene.Argument( CountryCodeEnum, description=( "Two-letter ISO 3166-1 country code. When provided, the exact quantity " "from a warehouse operating in shipping zones that contain this " "country will be returned. Otherwise, it will return the maximum " "quantity from all shipping zones." ), ), ) 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 @permission_required(ProductPermissions.MANAGE_PRODUCTS) def resolve_stocks(root: models.ProductVariant, info, country_code=None): if not country_code: return root.stocks.annotate_available_quantity().all() return root.stocks.annotate_available_quantity().for_country(country_code).all() @staticmethod def resolve_quantity_available( root: models.ProductVariant, info, country_code=None ): return get_available_quantity_for_customer(root, country_code) @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 def resolve_attributes(root: models.ProductVariant, info): return SelectedAttributesByProductVariantIdLoader(info.context).load(root.id) @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 def resolve_pricing(root: models.ProductVariant, info): context = info.context product = ProductByIdLoader(context).load(root.product_id) collections = CollectionsByProductIdLoader(context).load(root.product_id) def calculate_pricing_info(discounts): def calculate_pricing_with_product(product): def calculate_pricing_with_collections(collections): availability = get_variant_availability( variant=root, product=product, collections=collections, discounts=discounts, country=context.country, local_currency=context.currency, plugins=context.plugins, ) return VariantPricingInfo(**asdict(availability)) return collections.then(calculate_pricing_with_collections) return product.then(calculate_pricing_with_product) return ( DiscountsByDateTimeLoader(context) .load(info.context.request_time) .then(calculate_pricing_info) ) @staticmethod def resolve_product(root: models.ProductVariant, info): return ProductByIdLoader(info.context).load(root.product_id) @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, pk): 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 qs.filter(pk=pk).first() @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 Arguments: repositoryLocationName = graphene.NonNull(graphene.String)
class Arguments: runId = graphene.NonNull(graphene.String) terminatePolicy = graphene.Argument(GrapheneTerminatePipelinePolicy)
class Arguments: executionParams = graphene.NonNull(GrapheneExecutionParams)
class Arguments(object): email = graphene.NonNull(graphene.String) password = graphene.NonNull(graphene.String)
import graphene from graphene.types.objecttype import ObjectTypeOptions from .utils import camelize from .settings import graphene_settings ErrorType = graphene.List(graphene.NonNull(graphene.String), required=True) class ValidationErrorType(graphene.ObjectType): field = graphene.String(required=True) messages = graphene.List(graphene.NonNull(graphene.String), required=True) @classmethod def from_errors(cls, errors): data = camelize( errors) if graphene_settings.CAMELCASE_ERRORS else errors return [cls(field=key, messages=value) for key, value in data.items()] class BaseObjectTypeOptions(ObjectTypeOptions): view_factory = None filter_class = None class BaseType(graphene.ObjectType): created_at = graphene.String() updated_at = graphene.String() deleted_at = graphene.String() is_deleted = graphene.Boolean() @classmethod
class Input: lease_ids = graphene.List(graphene.NonNull(graphene.String), required=True) date = graphene.Date(required=True)
class ProductVariant(CountableDjangoObjectType, MetadataObjectType): 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="This field will be removed in Saleor 2.11. " "Use the stock field instead.", ) quantity_allocated = graphene.Int( required=False, description="Quantity allocated for orders", deprecation_reason="This field will be removed in Saleor 2.11. " "Use the stock field instead.", ) stock_quantity = graphene.Int( required=True, description="Quantity of a product available for sale.", deprecation_reason="This field will be removed in Saleor 2.11. " "Use the stock field instead.", ) 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="This field will be removed in Saleor 2.11. " "Use the stock field instead.", ) 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", ) stock = gql_optimizer.field( graphene.Field( graphene.List(Stock), description="Stocks for the product variant.", country=graphene.String(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] model = models.ProductVariant @staticmethod def resolve_stock(root: models.ProductVariant, info, country=None): if country is None: return gql_optimizer.query( root.stock.annotate_available_quantity().all(), info) return gql_optimizer.query( root.stock.annotate_available_quantity().for_country( "country").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): country = info.context.country try: stock = stock_models.Stock.objects.get_variant_stock_for_country( country, root) except stock_models.Stock.DoesNotExist: return 0 return get_available_quantity_for_customer(stock) @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, extensions=context.extensions, ) 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) @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, _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 GraphenePartitionSet(graphene.ObjectType): id = graphene.NonNull(graphene.ID) name = graphene.NonNull(graphene.String) pipeline_name = graphene.NonNull(graphene.String) solid_selection = graphene.List(graphene.NonNull(graphene.String)) mode = graphene.NonNull(graphene.String) partitionsOrError = graphene.Field( graphene.NonNull(GraphenePartitionsOrError), cursor=graphene.String(), limit=graphene.Int(), reverse=graphene.Boolean(), ) partition = graphene.Field(GraphenePartition, partition_name=graphene.NonNull(graphene.String)) partitionStatusesOrError = graphene.NonNull(GraphenePartitionStatusesOrError) repositoryOrigin = graphene.NonNull(GrapheneRepositoryOrigin) class Meta: name = "PartitionSet" def __init__(self, external_repository_handle, external_partition_set): self._external_repository_handle = check.inst_param( external_repository_handle, "external_respository_handle", RepositoryHandle ) self._external_partition_set = check.inst_param( external_partition_set, "external_partition_set", ExternalPartitionSet ) super().__init__( name=external_partition_set.name, pipeline_name=external_partition_set.pipeline_name, solid_selection=external_partition_set.solid_selection, mode=external_partition_set.mode, ) def resolve_id(self, _graphene_info): return self._external_partition_set.get_external_origin_id() def resolve_partitionsOrError(self, graphene_info, **kwargs): return get_partitions( graphene_info, self._external_repository_handle, self._external_partition_set, cursor=kwargs.get("cursor"), limit=kwargs.get("limit"), reverse=kwargs.get("reverse"), ) def resolve_partition(self, graphene_info, partition_name): return get_partition_by_name( graphene_info, self._external_repository_handle, self._external_partition_set, partition_name, ) def resolve_partitionStatusesOrError(self, graphene_info): return get_partition_set_partition_statuses( graphene_info, self._external_repository_handle, self._external_partition_set.name ) def resolve_repositoryOrigin(self, _): origin = self._external_partition_set.get_external_origin().external_repository_origin return GrapheneRepositoryOrigin(origin)
class Product(CountableDjangoObjectType, MetadataObjectType): url = graphene.String(description="The storefront URL for the product.", required=True) 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") slug = graphene.String(required=True, description="The slug of a product.") 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_type(root: models.Product, info): tax_data = info.context.extensions.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 root.get_absolute_url() @staticmethod @gql_optimizer.resolver_hints( prefetch_related=("variants", "collections"), only=[ "publication_date", "charge_taxes", "price_amount", "currency", "meta" ], ) def resolve_pricing(root: models.Product, info): context = info.context availability = get_product_availability( root, context.discounts, context.country, context.currency, context.extensions, ) 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", "meta" ], ) def resolve_price(root: models.Product, info): price_range = root.get_price_range(info.context.discounts) price = info.context.extensions.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, _info): return resolve_private_meta(root, _info) @staticmethod def resolve_meta(root, _info): return resolve_meta(root, _info) @staticmethod def resolve_slug(root: models.Product, *_args): return root.get_slug() @staticmethod def __resolve_reference(root, _info, **_kwargs): return graphene.Node.get_node_from_global_id(_info, root.id)
class GraphenePartition(graphene.ObjectType): name = graphene.NonNull(graphene.String) partition_set_name = graphene.NonNull(graphene.String) solid_selection = graphene.List(graphene.NonNull(graphene.String)) mode = graphene.NonNull(graphene.String) runConfigOrError = graphene.NonNull(GraphenePartitionRunConfigOrError) tagsOrError = graphene.NonNull(GraphenePartitionTagsOrError) runs = graphene.Field( non_null_list(GrapheneRun), filter=graphene.Argument(GrapheneRunsFilter), cursor=graphene.String(), limit=graphene.Int(), ) status = graphene.Field(GrapheneRunStatus) class Meta: name = "Partition" def __init__(self, external_repository_handle, external_partition_set, partition_name): self._external_repository_handle = check.inst_param( external_repository_handle, "external_respository_handle", RepositoryHandle ) self._external_partition_set = check.inst_param( external_partition_set, "external_partition_set", ExternalPartitionSet ) self._partition_name = check.str_param(partition_name, "partition_name") super().__init__( name=partition_name, partition_set_name=external_partition_set.name, solid_selection=external_partition_set.solid_selection, mode=external_partition_set.mode, ) def resolve_runConfigOrError(self, graphene_info): return get_partition_config( graphene_info, self._external_repository_handle, self._external_partition_set.name, self._partition_name, ) def resolve_tagsOrError(self, graphene_info): return get_partition_tags( graphene_info, self._external_repository_handle, self._external_partition_set.name, self._partition_name, ) def resolve_runs(self, graphene_info, **kwargs): filters = kwargs.get("filter") partition_tags = { PARTITION_SET_TAG: self._external_partition_set.name, PARTITION_NAME_TAG: self._partition_name, } if filters is not None: filters = filters.to_selector() runs_filter = RunsFilter( run_ids=filters.run_ids, pipeline_name=filters.job_name, statuses=filters.statuses, tags=merge_dicts(filters.tags, partition_tags), ) else: runs_filter = RunsFilter(tags=partition_tags) return get_runs( graphene_info, runs_filter, cursor=kwargs.get("cursor"), limit=kwargs.get("limit") )
class ProductVariantBulkCreate(BaseMutation): count = graphene.Int( required=True, default_value=0, description="Returns how many objects were created.", ) product_variants = graphene.List( graphene.NonNull(ProductVariant), required=True, default_value=[], description="List of the created variants.", ) class Arguments: variants = graphene.List( ProductVariantBulkCreateInput, required=True, description="Input list of product variants to create.", ) product_id = graphene.ID( description="ID of the product to create the variants for.", name="product", required=True, ) class Meta: description = "Creates product variants for a given product." permissions = (ProductPermissions.MANAGE_PRODUCTS, ) error_type_class = BulkProductError error_type_field = "bulk_product_errors" @classmethod def clean_variant_input( cls, info, instance: models.ProductVariant, data: dict, errors: dict, variant_index: int, ): cleaned_input = ModelMutation.clean_input( info, instance, data, input_cls=ProductVariantBulkCreateInput) attributes = cleaned_input.get("attributes") if attributes: try: cleaned_input[ "attributes"] = ProductVariantCreate.clean_attributes( attributes, data["product_type"]) except ValidationError as exc: exc.params = {"index": variant_index} errors["attributes"] = exc channel_listings = cleaned_input.get("channel_listings") if channel_listings: cleaned_input["channel_listings"] = cls.clean_channel_listings( channel_listings, errors, data["product"], variant_index) stocks = cleaned_input.get("stocks") if stocks: cls.clean_stocks(stocks, errors, variant_index) return cleaned_input @classmethod def clean_price(cls, price, field_name, currency, channel_id, variant_index, errors): try: validate_price_precision(price, currency) except ValidationError as error: error.code = ProductErrorCode.INVALID.value error.params = { "channels": [channel_id], "index": variant_index, } errors[field_name].append(error) @classmethod def clean_channel_listings(cls, channels_data, errors, product, variant_index): channel_ids = [ channel_listing["channel_id"] for channel_listing in channels_data ] duplicates = get_duplicated_values(channel_ids) if duplicates: errors["channel_listings"] = ValidationError( "Duplicated channel ID.", code=ProductErrorCode.DUPLICATED_INPUT_ITEM.value, params={ "channels": duplicates, "index": variant_index }, ) return channels_data channels = cls.get_nodes_or_error(channel_ids, "channel_listings", only_type=Channel) for index, channel_listing_data in enumerate(channels_data): channel_listing_data["channel"] = channels[index] for channel_listing_data in channels_data: price = channel_listing_data.get("price") cost_price = channel_listing_data.get("cost_price") channel_id = channel_listing_data["channel_id"] currency_code = channel_listing_data["channel"].currency_code cls.clean_price(price, "price", currency_code, channel_id, variant_index, errors) cls.clean_price( cost_price, "cost_price", currency_code, channel_id, variant_index, errors, ) channels_not_assigned_to_product = [] channels_assigned_to_product = list( models.ProductChannelListing.objects.filter( product=product.id).values_list("channel_id", flat=True)) for channel_listing_data in channels_data: if not channel_listing_data[ "channel"].id in channels_assigned_to_product: channels_not_assigned_to_product.append( channel_listing_data["channel_id"]) if channels_not_assigned_to_product: errors["channel_id"].append( ValidationError( "Product not available in channels.", code=ProductErrorCode.PRODUCT_NOT_ASSIGNED_TO_CHANNEL. value, params={ "index": variant_index, "channels": channels_not_assigned_to_product, }, )) return channels_data @classmethod def clean_stocks(cls, stocks_data, errors, variant_index): warehouse_ids = [stock["warehouse"] for stock in stocks_data] duplicates = get_duplicated_values(warehouse_ids) if duplicates: errors["stocks"] = ValidationError( "Duplicated warehouse ID.", code=ProductErrorCode.DUPLICATED_INPUT_ITEM.value, params={ "warehouses": duplicates, "index": variant_index }, ) @classmethod def add_indexes_to_errors(cls, index, error, error_dict): """Append errors with index in params to mutation error dict.""" for key, value in error.error_dict.items(): for e in value: if e.params: e.params["index"] = index else: e.params = {"index": index} error_dict[key].extend(value) @classmethod def save(cls, info, instance, cleaned_input): instance.save() attributes = cleaned_input.get("attributes") if attributes: AttributeAssignmentMixin.save(instance, attributes) generate_and_set_variant_name(instance, cleaned_input.get("sku")) @classmethod def create_variants(cls, info, cleaned_inputs, product, errors): instances = [] for index, cleaned_input in enumerate(cleaned_inputs): if not cleaned_input: continue try: instance = models.ProductVariant() cleaned_input["product"] = product instance = cls.construct_instance(instance, cleaned_input) cls.clean_instance(info, instance) instances.append(instance) except ValidationError as exc: cls.add_indexes_to_errors(index, exc, errors) return instances @classmethod def validate_duplicated_sku(cls, sku, index, sku_list, errors): if sku in sku_list: errors["sku"].append( ValidationError("Duplicated SKU.", ProductErrorCode.UNIQUE, params={"index": index})) sku_list.append(sku) @classmethod def validate_duplicated_attribute_values(cls, attributes_data, used_attribute_values, instance=None): attribute_values = defaultdict(list) for attr in attributes_data: attribute_values[attr.id].extend(attr.values) if attribute_values in used_attribute_values: raise ValidationError( "Duplicated attribute values for product variant.", ProductErrorCode.DUPLICATED_INPUT_ITEM, ) used_attribute_values.append(attribute_values) @classmethod def clean_variants(cls, info, variants, product, errors): cleaned_inputs = [] sku_list = [] used_attribute_values = get_used_variants_attribute_values(product) for index, variant_data in enumerate(variants): try: cls.validate_duplicated_attribute_values( variant_data.attributes, used_attribute_values) except ValidationError as exc: errors["attributes"].append( ValidationError(exc.message, exc.code, params={"index": index})) cleaned_input = None variant_data["product_type"] = product.product_type variant_data["product"] = product cleaned_input = cls.clean_variant_input(info, None, variant_data, errors, index) cleaned_inputs.append(cleaned_input if cleaned_input else None) if not variant_data.sku: continue cls.validate_duplicated_sku(variant_data.sku, index, sku_list, errors) return cleaned_inputs @classmethod def create_variant_channel_listings(cls, variant, cleaned_input): channel_listings_data = cleaned_input.get("channel_listings") if not channel_listings_data: return variant_channel_listings = [] for channel_listing_data in channel_listings_data: channel = channel_listing_data["channel"] price = channel_listing_data["price"] cost_price = channel_listing_data.get("cost_price") variant_channel_listings.append( models.ProductVariantChannelListing( channel=channel, variant=variant, price_amount=price, cost_price_amount=cost_price, currency=channel.currency_code, )) models.ProductVariantChannelListing.objects.bulk_create( variant_channel_listings) @classmethod @transaction.atomic def save_variants(cls, info, instances, product, cleaned_inputs): assert len(instances) == len( cleaned_inputs ), "There should be the same number of instances and cleaned inputs." for instance, cleaned_input in zip(instances, cleaned_inputs): cls.save(info, instance, cleaned_input) cls.create_variant_stocks(instance, cleaned_input) cls.create_variant_channel_listings(instance, cleaned_input) if not product.default_variant: product.default_variant = instances[0] product.save(update_fields=["default_variant", "updated_at"]) @classmethod def create_variant_stocks(cls, variant, cleaned_input): stocks = cleaned_input.get("stocks") if not stocks: return warehouse_ids = [stock["warehouse"] for stock in stocks] warehouses = cls.get_nodes_or_error(warehouse_ids, "warehouse", only_type=Warehouse) create_stocks(variant, stocks, warehouses) @classmethod def perform_mutation(cls, root, info, **data): product = cls.get_node_or_error(info, data["product_id"], models.Product) errors = defaultdict(list) cleaned_inputs = cls.clean_variants(info, data["variants"], product, errors) instances = cls.create_variants(info, cleaned_inputs, product, errors) if errors: raise ValidationError(errors) cls.save_variants(info, instances, product, cleaned_inputs) # Recalculate the "discounted price" for the parent product update_product_discounted_price_task.delay(product.pk) instances = [ ChannelContext(node=instance, channel_slug=None) for instance in instances ] return ProductVariantBulkCreate(count=len(instances), product_variants=instances)
class User(CountableDjangoObjectType): addresses = graphene.List(Address, description="List of all user's addresses.") checkout = graphene.Field( Checkout, description="Returns the last open checkout of this user.", deprecation_reason=( "Use the `checkout_tokens` field to fetch the user checkouts." ), ) checkout_tokens = graphene.List( graphene.NonNull(UUID), description="Returns the checkout UUID's assigned to this user.", channel=graphene.String( description="Slug of a channel for which the data should be returned." ), ) gift_cards = PrefetchingConnectionField( "saleor.graphql.giftcard.types.GiftCard", description="List of the user gift cards.", ) note = graphene.String(description="A note about the customer.") orders = PrefetchingConnectionField( "saleor.graphql.order.types.Order", description="List of user's orders." ) # deprecated, to remove in #5389 permissions = graphene.List( Permission, description="List of user's permissions.", deprecation_reason=( "Will be removed in Saleor 2.11." "Use the `userPermissions` instead." ), ) user_permissions = graphene.List( UserPermission, description="List of user's permissions." ) permission_groups = graphene.List( "saleor.graphql.account.types.Group", description="List of user's permission 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 = graphene.List( CustomerEvent, description="List of events associated with the user." ) stored_payment_sources = graphene.List( "saleor.graphql.payment.types.PaymentSource", description="List of stored payment sources.", ) store = graphene.Field( Store, id=graphene.Argument(graphene.ID, description="ID of the store."), slug=graphene.Argument(graphene.String, description="Slug of the store"), description="Look up a category by ID or slug.", ) 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", "is_supplier", "last_login", "last_name", "note", "store", ] @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) @staticmethod def resolve_checkout_tokens(root: models.User, info, channel=None, **_kwargs): def return_checkout_tokens(checkouts): if not checkouts: return [] checkout_global_ids = [] for checkout in checkouts: checkout_global_ids.append(checkout.token) return checkout_global_ids if not channel: return ( CheckoutByUserLoader(info.context) .load(root.id) .then(return_checkout_tokens) ) return ( CheckoutByUserAndChannelLoader(info.context) .load((root.id, channel)) .then(return_checkout_tokens) ) @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() # type: ignore return root.orders.confirmed() # type: ignore @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(info, root) raise PermissionDenied() @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)
class Meta: node = graphene.NonNull(self.node)
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( graphene.NonNull(PaymentGateway), description="List of available payment gateways.", required=True, ) email = graphene.String(description="Email of a customer.", required=True) gift_cards = graphene.List( GiftCard, description="List of gift cards associated with this checkout.") is_shipping_required = graphene.Boolean( description="Returns True, if checkout requires shipping.", required=True) lines = graphene.List( CheckoutLine, description=( "A list of checkout lines, each containing information about " "an item in the checkout."), ) shipping_price = graphene.Field( TaxedMoney, description="The price of the shipping, with all the taxes included.", ) shipping_method = graphene.Field( ShippingMethod, description="The shipping method related with checkout.", ) subtotal_price = graphene.Field( TaxedMoney, description= "The price of the checkout before shipping, with taxes included.", ) token = graphene.Field(UUID, description="The checkout's token.", required=True) 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", "channel", "note", "quantity", "shipping_address", "translated_discount_name", "user", "voucher_code", "discount", ] description = "Checkout object." model = models.Checkout interfaces = [graphene.relay.Node, ObjectWithMetadata] filter_fields = ["token"] @staticmethod def resolve_shipping_address(root: models.Checkout, info): if not root.shipping_address_id: return return AddressByIdLoader(info.context).load(root.shipping_address_id) @staticmethod def resolve_billing_address(root: models.Checkout, info): if not root.billing_address_id: return return AddressByIdLoader(info.context).load(root.billing_address_id) @staticmethod def resolve_user(root: models.Checkout, info): requestor = get_user_or_app_from_context(info.context) if requestor_has_access(requestor, root.user, AccountPermissions.MANAGE_USERS): return root.user raise PermissionDenied() @staticmethod def resolve_email(root: models.Checkout, _info): return root.get_customer_email() @staticmethod def resolve_shipping_method(root: models.Checkout, info): if not root.shipping_method_id: return None def wrap_shipping_method_with_channel_context(data): shipping_method, channel = data return ChannelContext(node=shipping_method, channel_slug=channel.slug) shipping_method = ShippingMethodByIdLoader(info.context).load( root.shipping_method_id) channel = ChannelByIdLoader(info.context).load(root.channel_id) return Promise.all([shipping_method, channel ]).then(wrap_shipping_method_with_channel_context) @staticmethod # TODO: We should optimize it in/after PR#5819 def resolve_total_price(root: models.Checkout, info): def calculate_total_price(data): address, lines, checkout_info, discounts = data taxed_total = (calculations.checkout_total( manager=info.context.plugins, checkout_info=checkout_info, lines=lines, address=address, discounts=discounts, ) - root.get_total_gift_cards_balance()) return max(taxed_total, zero_taxed_money(root.currency)) address_id = root.shipping_address_id or root.billing_address_id address = (AddressByIdLoader(info.context).load(address_id) if address_id else None) lines = CheckoutLinesInfoByCheckoutTokenLoader(info.context).load( root.token) checkout_info = CheckoutInfoByCheckoutTokenLoader(info.context).load( root.token) discounts = DiscountsByDateTimeLoader(info.context).load( info.context.request_time) return Promise.all([address, lines, checkout_info, discounts]).then(calculate_total_price) @staticmethod # TODO: We should optimize it in/after PR#5819 def resolve_subtotal_price(root: models.Checkout, info): def calculate_subtotal_price(data): address, lines, checkout_info, discounts = data return calculations.checkout_subtotal( manager=info.context.plugins, checkout_info=checkout_info, lines=lines, address=address, discounts=discounts, ) address_id = root.shipping_address_id or root.billing_address_id address = (AddressByIdLoader(info.context).load(address_id) if address_id else None) lines = CheckoutLinesInfoByCheckoutTokenLoader(info.context).load( root.token) checkout_info = CheckoutInfoByCheckoutTokenLoader(info.context).load( root.token) discounts = DiscountsByDateTimeLoader(info.context).load( info.context.request_time) return Promise.all([address, lines, checkout_info, discounts]).then(calculate_subtotal_price) @staticmethod # TODO: We should optimize it in/after PR#5819 def resolve_shipping_price(root: models.Checkout, info): def calculate_shipping_price(data): address, lines, checkout_info, discounts = data return calculations.checkout_shipping_price( manager=info.context.plugins, checkout_info=checkout_info, lines=lines, address=address, discounts=discounts, ) address = (AddressByIdLoader(info.context).load( root.shipping_address_id) if root.shipping_address_id else None) lines = CheckoutLinesInfoByCheckoutTokenLoader(info.context).load( root.token) checkout_info = CheckoutInfoByCheckoutTokenLoader(info.context).load( root.token) discounts = DiscountsByDateTimeLoader(info.context).load( info.context.request_time) return Promise.all([address, lines, checkout_info, discounts]).then(calculate_shipping_price) @staticmethod def resolve_lines(root: models.Checkout, info): return CheckoutLinesByCheckoutTokenLoader(info.context).load( root.token) @staticmethod # TODO: We should optimize it in/after PR#5819 def resolve_available_shipping_methods(root: models.Checkout, info): def calculate_available_shipping_methods(data): address, lines, checkout_info, discounts, channel = data channel_slug = channel.slug display_gross = info.context.site.settings.display_gross_prices manager = info.context.plugins subtotal = manager.calculate_checkout_subtotal( checkout_info, lines, address, discounts) if not address: return [] available = get_valid_shipping_methods_for_checkout( checkout_info, lines, discounts, subtotal=subtotal, country_code=address.country.code, ) if available is None: return [] available_ids = available.values_list("id", flat=True) def map_shipping_method_with_channel(shippings): def apply_price_to_shipping_method(channel_listings): channel_listing_map = { channel_listing.shipping_method_id: channel_listing for channel_listing in channel_listings } available_with_channel_context = [] for shipping in shippings: shipping_channel_listing = channel_listing_map[ shipping.id] taxed_price = info.context.plugins.apply_taxes_to_shipping( shipping_channel_listing.price, address) if display_gross: shipping.price = taxed_price.gross else: shipping.price = taxed_price.net available_with_channel_context.append( ChannelContext(node=shipping, channel_slug=channel_slug)) return available_with_channel_context map_shipping_method_and_channel = ( (shipping_method_id, channel_slug) for shipping_method_id in available_ids) return ( ShippingMethodChannelListingByShippingMethodIdAndChannelSlugLoader( info.context).load_many( map_shipping_method_and_channel).then( apply_price_to_shipping_method)) return (ShippingMethodByIdLoader(info.context).load_many( available_ids).then(map_shipping_method_with_channel)) channel = ChannelByIdLoader(info.context).load(root.channel_id) address = (AddressByIdLoader(info.context).load( root.shipping_address_id) if root.shipping_address_id else None) lines = CheckoutLinesInfoByCheckoutTokenLoader(info.context).load( root.token) checkout_info = CheckoutInfoByCheckoutTokenLoader(info.context).load( root.token) discounts = DiscountsByDateTimeLoader(info.context).load( info.context.request_time) return Promise.all([address, lines, checkout_info, discounts, channel ]).then(calculate_available_shipping_methods) @staticmethod def resolve_available_payment_gateways(root: models.Checkout, info): return info.context.plugins.checkout_available_payment_gateways( checkout=root) @staticmethod def resolve_gift_cards(root: models.Checkout, _info): return root.gift_cards.all() @staticmethod def resolve_is_shipping_required(root: models.Checkout, info): def is_shipping_required(lines): product_ids = [line_info.product.id for line_info in lines] def with_product_types(product_types): return any([pt.is_shipping_required for pt in product_types]) return (ProductTypeByProductIdLoader( info.context).load_many(product_ids).then(with_product_types)) return (CheckoutLinesInfoByCheckoutTokenLoader(info.context).load( root.token).then(is_shipping_required))
class Page(CountableDjangoObjectType): content_json = graphene.JSONString( description="Content of the page (JSON).", deprecation_reason=( "Will be removed in Saleor 4.0. Use the `content` field instead." ), required=True, ) translation = TranslationField(PageTranslation, type_name="page") attributes = graphene.List( graphene.NonNull(SelectedAttribute), required=True, description="List of attributes assigned to this product.", ) media = graphene.List( graphene.NonNull(lambda: PageMedia), description="List of media for the page.", ) store = graphene.Field( Store, id=graphene.Argument(graphene.ID, description="ID of the store."), slug=graphene.Argument(graphene.String, description="Slug of the store"), description="Look up a store by ID or slug.", ) class Meta: description = ( "A static page that can be manually added by a shop operator through the " "dashboard." ) only_fields = [ "content", "created", "id", "is_published", "page_type", "publication_date", "seo_description", "seo_title", "slug", "title", "store", ] interfaces = [graphene.relay.Node, ObjectWithMetadata] model = models.Page @staticmethod def resolve_page_type(root: models.Page, info): return PageTypeByIdLoader(info.context).load(root.page_type_id) @staticmethod def resolve_content_json(root: models.Page, info): content = root.content return content if content is not None else {} @staticmethod def resolve_attributes(root: models.Page, info): return SelectedAttributesByPageIdLoader(info.context).load(root.id) @staticmethod def resolve_media(self, info, page=None, slug=None, channel=None, **_kwargs): return models.PageMedia.objects.filter(page_id=self.pk, is_active=True)
class CollectionChannelListingError(ProductError): channels = graphene.List( graphene.NonNull(graphene.ID), description="List of channels IDs which causes the error.", required=False, )
class Arguments: data = graphene.NonNull(HostInput)
class GraphenePipelineRun(graphene.ObjectType): id = graphene.NonNull(graphene.ID) runId = graphene.NonNull(graphene.String) # Nullable because of historical runs pipelineSnapshotId = graphene.String() repositoryOrigin = graphene.Field(GrapheneRepositoryOrigin) status = graphene.NonNull(GraphenePipelineRunStatus) pipeline = graphene.NonNull(GraphenePipelineReference) pipelineName = graphene.NonNull(graphene.String) solidSelection = graphene.List(graphene.NonNull(graphene.String)) stats = graphene.NonNull(GraphenePipelineRunStatsOrError) stepStats = non_null_list(GraphenePipelineRunStepStats) computeLogs = graphene.Field( graphene.NonNull(GrapheneComputeLogs), stepKey=graphene.Argument(graphene.NonNull(graphene.String)), description=""" Compute logs are the stdout/stderr logs for a given solid step computation """, ) executionPlan = graphene.Field(GrapheneExecutionPlan) stepKeysToExecute = graphene.List(graphene.NonNull(graphene.String)) runConfigYaml = graphene.NonNull(graphene.String) mode = graphene.NonNull(graphene.String) tags = non_null_list(GraphenePipelineTag) rootRunId = graphene.Field(graphene.String) parentRunId = graphene.Field(graphene.String) canTerminate = graphene.NonNull(graphene.Boolean) assets = non_null_list(GrapheneAsset) class Meta: name = "PipelineRun" def __init__(self, pipeline_run): super().__init__( runId=pipeline_run.run_id, status=PipelineRunStatus(pipeline_run.status), mode=pipeline_run.mode, ) self._pipeline_run = check.inst_param(pipeline_run, "pipeline_run", PipelineRun) def resolve_id(self, _graphene_info): return self._pipeline_run.run_id def resolve_repositoryOrigin(self, _graphene_info): return (GrapheneRepositoryOrigin( self._pipeline_run.external_pipeline_origin. external_repository_origin) if self._pipeline_run.external_pipeline_origin else None) def resolve_pipeline(self, graphene_info): return get_pipeline_reference_or_raise(graphene_info, self._pipeline_run) def resolve_pipelineName(self, _graphene_info): return self._pipeline_run.pipeline_name def resolve_solidSelection(self, _graphene_info): return self._pipeline_run.solid_selection def resolve_pipelineSnapshotId(self, _graphene_info): return self._pipeline_run.pipeline_snapshot_id def resolve_stats(self, graphene_info): return get_stats(graphene_info, self.run_id) def resolve_stepStats(self, graphene_info): return get_step_stats(graphene_info, self.run_id) def resolve_computeLogs(self, _graphene_info, stepKey): return GrapheneComputeLogs(runId=self.run_id, stepKey=stepKey) def resolve_executionPlan(self, graphene_info): if not (self._pipeline_run.execution_plan_snapshot_id and self._pipeline_run.pipeline_snapshot_id): return None instance = graphene_info.context.instance historical_pipeline = instance.get_historical_pipeline( self._pipeline_run.pipeline_snapshot_id) execution_plan_snapshot = instance.get_execution_plan_snapshot( self._pipeline_run.execution_plan_snapshot_id) return (GrapheneExecutionPlan( ExternalExecutionPlan( execution_plan_snapshot=execution_plan_snapshot, represented_pipeline=historical_pipeline, )) if execution_plan_snapshot and historical_pipeline else None) def resolve_stepKeysToExecute(self, _graphene_info): return self._pipeline_run.step_keys_to_execute def resolve_runConfigYaml(self, _graphene_info): return yaml.dump(self._pipeline_run.run_config, default_flow_style=False, allow_unicode=True) def resolve_tags(self, _graphene_info): return [ GraphenePipelineTag(key=key, value=value) for key, value in self._pipeline_run.tags.items() if get_tag_type(key) != TagType.HIDDEN ] def resolve_rootRunId(self, _graphene_info): return self._pipeline_run.root_run_id def resolve_parentRunId(self, _graphene_info): return self._pipeline_run.parent_run_id @property def run_id(self): return self.runId def resolve_canTerminate(self, graphene_info): # short circuit if the pipeline run is in a terminal state if self._pipeline_run.is_finished: return False return graphene_info.context.instance.run_coordinator.can_cancel_run( self.run_id) def resolve_assets(self, graphene_info): return get_assets_for_run_id(graphene_info, self.run_id)
class GrapheneTerminatePipelineExecutionFailure(graphene.ObjectType): run = graphene.NonNull(GraphenePipelineRun) message = graphene.NonNull(graphene.String) class Meta: name = "TerminatePipelineExecutionFailure"