Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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)
Exemple #4
0
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()
Exemple #5
0
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)
Exemple #6
0
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)
Exemple #7
0
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)
Exemple #8
0
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
Exemple #9
0
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()
Exemple #10
0
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
Exemple #11
0
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)
Exemple #12
0
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)
Exemple #13
0
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
Exemple #14
0
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)
Exemple #15
0
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
Exemple #16
0
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
Exemple #17
0
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)
Exemple #18
0
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
        ]
Exemple #19
0
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)
Exemple #20
0
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()
Exemple #21
0
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()
Exemple #22
0
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.")
Exemple #23
0
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)
Exemple #24
0
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)
Exemple #25
0
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)
Exemple #26
0
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
Exemple #27
0
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
Exemple #28
0
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
Exemple #29
0
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)
Exemple #30
0
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)