Exemple #1
0
class GrapheneSchedule(graphene.ObjectType):
    id = graphene.NonNull(graphene.ID)
    name = graphene.NonNull(graphene.String)
    cron_schedule = graphene.NonNull(graphene.String)
    pipeline_name = graphene.NonNull(graphene.String)
    solid_selection = graphene.List(graphene.String)
    mode = graphene.NonNull(graphene.String)
    execution_timezone = graphene.Field(graphene.String)
    scheduleState = graphene.NonNull(GrapheneJobState)
    partition_set = graphene.Field(
        "dagster_graphql.schema.partition_sets.GraphenePartitionSet")
    futureTicks = graphene.NonNull(GrapheneFutureJobTicks,
                                   cursor=graphene.Float(),
                                   limit=graphene.Int())
    futureTick = graphene.NonNull(GrapheneFutureJobTick,
                                  tick_timestamp=graphene.NonNull(
                                      graphene.Int))

    class Meta:
        name = "Schedule"

    def __init__(self, graphene_info, external_schedule):
        self._external_schedule = check.inst_param(external_schedule,
                                                   "external_schedule",
                                                   ExternalSchedule)
        self._schedule_state = graphene_info.context.instance.get_job_state(
            self._external_schedule.get_external_origin_id())

        if not self._schedule_state:
            # Also include a ScheduleState for a stopped schedule that may not
            # have a stored database row yet
            self._schedule_state = self._external_schedule.get_default_job_state(
                graphene_info.context.instance)

        super().__init__(
            name=external_schedule.name,
            cron_schedule=external_schedule.cron_schedule,
            pipeline_name=external_schedule.pipeline_name,
            solid_selection=external_schedule.solid_selection,
            mode=external_schedule.mode,
            scheduleState=GrapheneJobState(self._schedule_state),
            execution_timezone=(self._external_schedule.execution_timezone
                                if self._external_schedule.execution_timezone
                                else pendulum.now().timezone.name),
        )

    def resolve_id(self, _):
        return "%s:%s" % (self.name, self.pipeline_name)

    def resolve_partition_set(self, graphene_info):
        from ..partition_sets import GraphenePartitionSet

        if self._external_schedule.partition_set_name is None:
            return None

        repository = graphene_info.context.get_repository_location(
            self._external_schedule.handle.location_name).get_repository(
                self._external_schedule.handle.repository_name)
        external_partition_set = repository.get_external_partition_set(
            self._external_schedule.partition_set_name)

        return GraphenePartitionSet(
            external_repository_handle=repository.handle,
            external_partition_set=external_partition_set,
        )

    def resolve_futureTicks(self, _graphene_info, **kwargs):
        cursor = kwargs.get(
            "cursor",
            get_timestamp_from_utc_datetime(get_current_datetime_in_utc()))
        limit = kwargs.get("limit", 10)

        tick_times = []
        time_iter = self._external_schedule.execution_time_iterator(cursor)

        for _ in range(limit):
            tick_times.append(next(time_iter).timestamp())

        future_ticks = [
            GrapheneFutureJobTick(self._schedule_state, tick_time)
            for tick_time in tick_times
        ]

        return GrapheneFutureJobTicks(results=future_ticks,
                                      cursor=tick_times[-1] + 1)

    def resolve_futureTick(self, _graphene_info, tick_timestamp):
        return GrapheneFutureJobTick(self._schedule_state,
                                     float(tick_timestamp))
Exemple #2
0
    class Query(graphene.ObjectType):
        reporter = graphene.Field(ReporterType)

        def resolve_reporter(self, info):
            return SimpleLazyObject(
                lambda: SimpleLazyObject(lambda: Reporter(id=1)))
Exemple #3
0
class Category(CountableDjangoObjectType):
    description_json = graphene.JSONString(
        description="Description of the category (JSON).",
        deprecation_reason=
        ("Will be removed in Saleor 4.0. Use the `description` field instead."
         ),
    )
    ancestors = PrefetchingConnectionField(
        lambda: Category, description="List of ancestors of the category.")
    products = ChannelContextFilterConnectionField(
        Product,
        channel=graphene.String(
            description=
            "Slug of a channel for which the data should be returned."),
        description="List of products in the category.",
    )
    url = graphene.String(
        description="The storefront's URL for the category.",
        deprecation_reason="This field will be removed after 2020-07-31.",
    )
    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 = TranslationField(CategoryTranslation, type_name="category")

    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",
            "id",
            "level",
            "name",
            "parent",
            "seo_description",
            "seo_title",
            "slug",
        ]
        interfaces = [relay.Node, ObjectWithMetadata]
        model = models.Category

    @staticmethod
    def resolve_ancestors(root: models.Category, info, **_kwargs):
        return root.get_ancestors()

    @staticmethod
    def resolve_description_json(root: models.Category, info):
        description = root.description
        return description if description is not None else {}

    @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):
        return root.children.all()

    @staticmethod
    def resolve_url(root: models.Category, _info):
        return ""

    @staticmethod
    def resolve_products(root: models.Category, info, channel=None, **_kwargs):
        requestor = get_user_or_app_from_context(info.context)
        is_staff = requestor_is_staff_member_or_app(requestor)
        tree = root.get_descendants(include_self=True)
        if channel is None and not is_staff:
            channel = get_default_channel_slug_or_graphql_error()
        qs = models.Product.objects.all()
        if not is_staff:
            qs = (qs.published(channel).annotate_visible_in_listings(
                channel).exclude(visible_in_listings=False, ))
        if channel and is_staff:
            qs = qs.filter(channel_listings__channel__slug=channel)
        qs = qs.filter(category__in=tree)
        return ChannelQsContext(qs=qs, channel_slug=channel)

    @staticmethod
    def __resolve_reference(root, _info, **_kwargs):
        return graphene.Node.get_node_from_global_id(_info, root.id)
    class PaginatedType(BasePaginatedType):
        items = graphene.List(of_type, required=required)
        pagination = graphene.Field(PaginationType, required=required)

        class Meta:
            name = type_name + "PaginatedType"
class OrderFulfill(BaseMutation):
    fulfillments = graphene.List(
        Fulfillment, description="List of created fulfillments."
    )
    order = graphene.Field(Order, description="Fulfilled order.")

    class Arguments:
        order = graphene.ID(
            description="ID of the order to be fulfilled.", name="order"
        )
        input = OrderFulfillInput(
            required=True, description="Fields required to create an fulfillment."
        )

    class Meta:
        description = "Creates new fulfillments for an order."
        permissions = (OrderPermissions.MANAGE_ORDERS,)
        error_type_class = OrderError
        error_type_field = "order_errors"

    @classmethod
    def clean_lines(cls, order_lines, quantities):
        for order_line, line_quantities in zip(order_lines, quantities):
            line_quantity_unfulfilled = order_line.quantity_unfulfilled

            if sum(line_quantities) > line_quantity_unfulfilled:
                msg = (
                    "Only %(quantity)d item%(item_pluralize)s remaining "
                    "to fulfill: %(order_line)s."
                ) % {
                    "quantity": line_quantity_unfulfilled,
                    "item_pluralize": pluralize(line_quantity_unfulfilled),
                    "order_line": order_line,
                }
                order_line_global_id = graphene.Node.to_global_id(
                    "OrderLine", order_line.pk
                )
                raise ValidationError(
                    {
                        "order_line_id": ValidationError(
                            msg,
                            code=OrderErrorCode.FULFILL_ORDER_LINE,
                            params={"order_line": order_line_global_id},
                        )
                    }
                )

    @classmethod
    def check_warehouses_for_duplicates(cls, warehouse_ids):
        for warehouse_ids_for_line in warehouse_ids:
            duplicates = get_duplicated_values(warehouse_ids_for_line)
            if duplicates:
                raise ValidationError(
                    {
                        "warehouse": ValidationError(
                            "Duplicated warehouse ID.",
                            code=OrderErrorCode.DUPLICATED_INPUT_ITEM,
                            params={"warehouse": duplicates.pop()},
                        )
                    }
                )

    @classmethod
    def check_lines_for_duplicates(cls, lines_ids):
        duplicates = get_duplicated_values(lines_ids)
        if duplicates:
            raise ValidationError(
                {
                    "orderLineId": ValidationError(
                        "Duplicated order line ID.",
                        code=OrderErrorCode.DUPLICATED_INPUT_ITEM,
                        params={"order_line": duplicates.pop()},
                    )
                }
            )

    @classmethod
    def check_total_quantity_of_items(cls, quantities_for_lines):
        flat_quantities = sum(quantities_for_lines, [])
        if sum(flat_quantities) <= 0:
            raise ValidationError(
                {
                    "lines": ValidationError(
                        "Total quantity must be larger than 0.",
                        code=OrderErrorCode.ZERO_QUANTITY,
                    )
                }
            )

    @classmethod
    def clean_input(cls, data):
        lines = data["lines"]

        warehouse_ids_for_lines = [
            [stock["warehouse"] for stock in line["stocks"]] for line in lines
        ]
        cls.check_warehouses_for_duplicates(warehouse_ids_for_lines)

        quantities_for_lines = [
            [stock["quantity"] for stock in line["stocks"]] for line in lines
        ]

        lines_ids = [line["order_line_id"] for line in lines]
        cls.check_lines_for_duplicates(lines_ids)
        order_lines = cls.get_nodes_or_error(
            lines_ids, field="lines", only_type=OrderLine
        )

        cls.clean_lines(order_lines, quantities_for_lines)

        cls.check_total_quantity_of_items(quantities_for_lines)

        lines_for_warehouses = defaultdict(list)
        for line, order_line in zip(lines, order_lines):
            for stock in line["stocks"]:
                if stock["quantity"] > 0:
                    warehouse_pk = from_global_id_strict_type(
                        stock["warehouse"], only_type=Warehouse, field="warehouse"
                    )
                    lines_for_warehouses[warehouse_pk].append(
                        {"order_line": order_line, "quantity": stock["quantity"]}
                    )

        data["order_lines"] = order_lines
        data["quantities"] = quantities_for_lines
        data["lines_for_warehouses"] = lines_for_warehouses
        return data

    @classmethod
    def perform_mutation(cls, _root, info, order, **data):
        order = cls.get_node_or_error(info, order, field="order", only_type=Order)
        data = data.get("input")

        cleaned_input = cls.clean_input(data)

        user = info.context.user
        lines_for_warehouses = cleaned_input["lines_for_warehouses"]
        notify_customer = cleaned_input.get("notify_customer", True)

        try:
            fulfillments = create_fulfillments(
                user,
                order,
                dict(lines_for_warehouses),
                info.context.plugins,
                notify_customer,
            )
        except InsufficientStock as exc:
            errors = prepare_insufficient_stock_order_validation_errors(exc)
            raise ValidationError({"stocks": errors})

        return OrderFulfill(fulfillments=fulfillments, order=order)
Exemple #6
0
class Query(graphene.ObjectType):
    user = graphene.Field(UserType, id=graphene.Int(required=True))

    def resolve_user(self, info, id):
        return get_user_model().objects.get(id=id)
Exemple #7
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'))

    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_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, info):
        return self.get_absolute_url()

    @gql_optimizer.resolver_hints(
        prefetch_related='variants',
        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, 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()

    def resolve_available_on(self, info):
        return self.publication_date
Exemple #8
0
    class Query(graphene.ObjectType):
        all_reporters = graphene.List(ReporterType)
        debug = graphene.Field(DjangoDebug, name="__debug")

        def resolve_all_reporters(self, info, **args):
            return Reporter.objects.all()
Exemple #9
0
class CheckoutLinesAdd(BaseMutation):
    checkout = graphene.Field(Checkout, description='An updated Checkout.')

    class Arguments:
        checkout_id = graphene.ID(description='The ID of the Checkout.',
                                  required=True)
        lines = graphene.List(
            CheckoutLineInput,
            required=True,
            description=(
                'A list of checkout lines, each containing information about '
                'an item in the checkout.'))

    class Meta:
        description = 'Adds a checkout line to the existing checkout.'

    @classmethod
    def mutate(cls, root, info, checkout_id, lines, replace=False):
        errors = []
        checkout = cls.get_node_or_error(info,
                                         checkout_id,
                                         errors,
                                         'checkout_id',
                                         only_type=Checkout)
        if checkout is None:
            return CheckoutLinesAdd(errors=errors)

        variants, quantities = None, None
        if lines:
            variant_ids = [line.get('variant_id') for line in lines]
            variants = cls.get_nodes_or_error(ids=variant_ids,
                                              errors=errors,
                                              field='variant_id',
                                              only_type=ProductVariant)
            quantities = [line.get('quantity') for line in lines]
            if not errors:
                line_errors = check_lines_quantity(variants, quantities)
                if line_errors:
                    for err in line_errors:
                        cls.add_error(errors, field=err[0], message=err[1])

        # FIXME test if below function is called
        clean_shipping_method(checkout=checkout,
                              method=checkout.shipping_method,
                              errors=errors,
                              discounts=info.context.discounts,
                              taxes=get_taxes_for_address(
                                  checkout.shipping_address))

        if errors:
            return CheckoutLinesAdd(errors=errors)

        if variants and quantities:
            for variant, quantity in zip(variants, quantities):
                add_variant_to_cart(checkout,
                                    variant,
                                    quantity,
                                    replace=replace)

        recalculate_cart_discount(checkout, info.context.discounts,
                                  info.context.taxes)

        return CheckoutLinesAdd(checkout=checkout, errors=errors)
Exemple #10
0
class Query(object):
    users_single = graphene.Field(UserType,
                                  id=graphene.Int(name="_id"),
                                  slug=graphene.String(),
                                  document_id=graphene.String(),
                                  name="UsersSingle")
    all_users = graphene.List(UserType)
    post = graphene.Field(Post, name="Post")
    posts_single = graphene.Field(
        Post,
        _id=graphene.String(name="_id"),
        posted_at=graphene.types.datetime.DateTime(),
        frontpage_date=graphene.types.datetime.Date(),
        curated_date=graphene.types.datetime.Date(),
        userId=graphene.String(),
        document_id=graphene.String(),
        name="PostsSingle")
    all_posts = graphene.List(Post)
    posts_list = graphene.Field(graphene.List(Post),
                                terms=graphene.Argument(PostsTerms),
                                name="PostsList")
    posts_new = graphene.Field(Post,
                               _id=graphene.String(name="_id"),
                               slug=graphene.String(),
                               name="PostsNew")
    posts_edit = graphene.Field(Post,
                                _id=graphene.String(name="_id"),
                                slug=graphene.String(),
                                name="PostsEdit")
    comment = graphene.Field(Comment,
                             id=graphene.String(),
                             posted_at=graphene.types.datetime.Date(),
                             userId=graphene.Int())
    all_comments = graphene.List(Comment)

    comments_total = graphene.Field(graphene.types.Int,
                                    terms=graphene.Argument(CommentsTerms),
                                    name="CommentsTotal")

    comments_list = graphene.Field(graphene.List(Comment),
                                   terms=graphene.Argument(CommentsTerms),
                                   name="CommentsList")
    comments_new = graphene.Field(Comment, _id=graphene.String(name="_id"))

    comments_edit = graphene.Field(Comment, _id=graphene.String(name="_id"))

    vote = graphene.Field(VoteType, id=graphene.Int())

    all_votes = graphene.List(VoteType)

    notifications_list = graphene.Field(
        graphene.List(NotificationType),
        terms=graphene.Argument(NotificationsTerms),
        name="NotificationsList")
    conversations_single = graphene.Field(ConversationType,
                                          document_id=graphene.String(),
                                          name="ConversationsSingle")
    messages_list = graphene.Field(graphene.List(Message),
                                   terms=graphene.Argument(MessagesTerms),
                                   name="MessagesList")

    def resolve_users_single(self, info, **kwargs):
        id = kwargs.get('id')
        document_id = kwargs.get('document_id')
        slug = kwargs.get('slug')

        if id:
            id = int(id)
            return User.objects.get(id=id)
        if document_id:
            document_id = int(document_id)
            # Mongodb uses this field for uid lookups, we do it for compatibility
            return User.objects.get(id=document_id)
        if slug:
            return User.objects.get(username=slug)

        raise ValueError(
            "No identifying field passed to resolver.  Please use ID, slug, etc."
        )

    def resolve_all_users(self, info, **kwargs):
        return User.objects.all()

    def resolve_posts_single(self, info, **kwargs):
        id = kwargs.get('document_id')
        if id:
            return PostModel.objects.get(id=id)

        raise ValueError("No post with ID '{}' found.".format(id))

    def resolve_all_posts(self, info, **kwargs):
        #TODO: Figure out a better way to maintain compatibility here
        #...If there is one.
        return PostModel.objects.all().annotate(test=Greatest(
            'posted_at', 'comments__posted_at')).order_by('-test')

    def resolve_posts_list(self, info, **kwargs):
        args = kwargs.get("terms")
        if args.user_id:
            user = User.objects.get(id=args.user_id)
            return PostModel.objects.filter(user=user)
        if args.limit and args.offset:
            return PostModel.objects.all().annotate(
                test=Greatest('posted_at', 'comments__posted_at')).order_by(
                    '-test')[args.offset:args.offset + args.limit]
        elif args.limit:
            return PostModel.objects.all().annotate(
                test=Greatest('posted_at', 'comments__posted_at')).order_by(
                    '-test')[:args.limit]
        return PostModel.objects.all().annotate(test=Greatest(
            'posted_at', 'comments__posted_at')).order_by('-test')

    def resolve_comment(self, info, **kwargs):
        id = kwargs.get('id')

        if id:
            return PostModel.objects.get(id=id)

        raise ValueError("No comment with ID '{}' found.".format(id))

    def resolve_all_comments(self, info, **kwargs):
        return CommentModel.objects.select_related('post').all()

    def resolve_comments_total(self, info, **kwargs):
        args = dict(kwargs.get('terms'))
        id = args.get('post_id')
        try:
            return PostModel.objects.get(id=id).comment_count
        except:
            return 0

    def resolve_comments_list(self, info, **kwargs):
        args = dict(kwargs.get('terms'))
        if "user_id" in args:
            user = User.objects.get(id=int(args["user_id"]))
            return CommentModel.objects.filter(user=user)
        elif "post_id" in args:
            try:
                document = PostModel.objects.get(id=args["post_id"])
                return document.comments.all()
            except:
                return graphene.List(Comment, resolver=lambda x, y: [])
        else:
            return CommentModel.objects.all().order_by('-posted_at')

    def resolve_vote(self, info, **kwargs):
        id = kwargs.get('id')

        if id:
            return Vote.objects.get(id=id)

        raise ValueError("No vote found for ID '{}'.".format(id))

    def resolve_notifications_list(self, info, **kwargs):
        args = kwargs["terms"]
        if not args.user_id:
            raise ValueError("No user with ID '{}' found".format(
                info.context.user.id))
        if args.view == "userNotifications":
            user = User.objects.get(id=args.user_id)
            #TODO: Implement offset
            if args.limit:
                return Notification.objects.filter(user=user)[:args.limit]
            else:
                return Notification.objects.filter(user=user)

    def resolve_conversations_single(self, info, **kwargs):
        document_id = kwargs["document_id"]
        if document_id:
            return Conversation.objects.get(id=int(document_id))
        else:
            raise ValueError("Expected document id, instead got '{}'".format(
                repr(document_id)))

    def resolve_messages_list(self, info, **kwargs):
        #if not info.context.user.is_authenticated:
        #    raise ValueError("Need to be logged in to read private messages!")
        convo_id = kwargs["terms"].conversation_id
        return Conversation.objects.get(id=int(convo_id)).messages.all()
Exemple #11
0
    class Query(graphene.ObjectType):
        reporter = graphene.Field(ReporterType)
        debug = graphene.Field(DjangoDebug, name="__debug")

        def resolve_reporter(self, info, **args):
            return Reporter.objects.first()
Exemple #12
0
class Query(graphene.ObjectType):

    framework = graphene.List(FrameWorkType)

    language = graphene.List(LanguageType)

    framework_by_number = graphene.Field(FrameWorkType, number=graphene.Int())

    language_by_number = graphene.Field(FrameWorkType, number=graphene.Int())

    framework_by_topic = graphene.Field(FrameWorkType, name=graphene.String())

    language_by_topic = graphene.Field(FrameWorkType, name=graphene.String())

    comment_language = graphene.List(CommentLanugageType)

    comment_framework = graphene.List(CommentFrameWorkType)

    comment_language_by_topic = graphene.Field(CommentLanugageType,
                                               name=graphene.String(),
                                               number=graphene.Int())

    comment_framework_by_topic = graphene.Field(CommentFrameWorkType,
                                                name=graphene.String(),
                                                number=graphene.Int())

    language_specific = graphene.Field(LanguageType,
                                       name=graphene.String(),
                                       number=graphene.Int())

    framework_specific = graphene.Field(FrameWorkType,
                                        name=graphene.String(),
                                        number=graphene.Int())

    # passed
    def resolve_framework(root, info, **kwargs):
        # Return All of the FrameWorks
        # Querying a list
        return FrameWork.objects.all()

    # passed
    def resolve_language(root, info, **kwargs):
        # Return All of the FrameWorks
        # Querying a list
        return Language.objects.all()

    def resolve_language_by_number(root, info, number):
        return get_list_or_404(Language, number=number)

    def resolve_framework_by_number(root, info, number):
        return get_list_or_404(FrameWork, number=number)

    def resolve_language_by_topic(root, info, name):
        return get_list_or_404(Language, name=name)

    def resolve_framework_by_topic(root, info, name):
        return get_list_or_404(FrameWork, lang=name)

    # passed

    def resolve_language_specific(root, info, name, number):
        return get_object_or_404(Language, name=name, number=number)

    # passed

    def resolve_framework_specific(root, info, name, number):
        return get_object_or_404(FrameWork, lang=name, number=number)

    def resolve_language_comment(root, info, **kwargs):
        return CommentLanguage.objects.all()

    def resolve_framework_comment(root, info, **kwargs):
        return CommentFrameWork.objects.all()

    def resolve_comment_language_by_topic(root, info, name, number):

        return CommentLanguage.objects.filter(name=name, number=number)

    def resolve_comment_framework_by_topic(root, info, name, number):

        return CommentFrameWork.objects.filter(lang=name, number=number)
Exemple #13
0
class UpdateDiscussionPhase(graphene.Mutation):
    __doc__ = docs.UpdateDiscussionPhase.__doc__

    class Input:
        id = graphene.ID(required=True,
                         description=docs.UpdateDiscussionPhase.id)
        is_thematics_table = graphene.Boolean(
            description=docs.UpdateDiscussionPhase.is_thematics_table)
        lang = graphene.String(required=True,
                               description=docs.UpdateDiscussionPhase.lang)
        identifier = graphene.String(
            required=True, description=docs.UpdateDiscussionPhase.identifier)
        title_entries = graphene.List(
            LangStringEntryInput,
            required=True,
            description=docs.UpdateDiscussionPhase.title_entries)
        description_entries = graphene.List(
            LangStringEntryInput,
            required=False,
            description=docs.UpdateDiscussionPhase.description_entries)
        start = DateTime(required=True,
                         description=docs.UpdateDiscussionPhase.start)
        end = DateTime(required=True,
                       description=docs.UpdateDiscussionPhase.end)
        image = graphene.String(description=docs.UpdateDiscussionPhase.image)
        order = graphene.Float(required=True,
                               description=docs.UpdateDiscussionPhase.order)

    discussion_phase = graphene.Field(lambda: DiscussionPhase)

    @staticmethod
    @abort_transaction_on_exception
    def mutate(root, args, context, info):
        cls = models.DiscussionPhase
        phase_id = args.get('id')
        phase_id = int(Node.from_global_id(phase_id)[1])
        phase = cls.get(phase_id)
        require_instance_permission(CrudPermissions.UPDATE, phase, context)
        with cls.default_db.no_autoflush as db:
            title_entries = args.get('title_entries')
            if len(title_entries) == 0:
                raise Exception(
                    'DiscussionPhase titleEntries needs at least one entry')

            update_langstring_from_input_entries(phase, 'title', title_entries)
            description_entries = args.get('description_entries')
            if description_entries is not None:
                update_langstring_from_input_entries(phase, 'description',
                                                     description_entries)

            identifier = args.get('identifier')
            phase.identifier = identifier
            #            phase.is_thematics_table = is_thematics_table
            # SQLAlchemy wants naive datetimes
            phase.start = args.get('start').replace(tzinfo=None)
            phase.end = args.get('end').replace(tzinfo=None)
            phase.order = args.get('order')
            image = args.get('image')
            discussion_id = context.matchdict['discussion_id']
            discussion = models.Discussion.get(discussion_id)
            if image is not None:
                update_attachment(discussion, models.TimelineEventAttachment,
                                  image, phase.attachments,
                                  models.AttachmentPurpose.IMAGE.value, db,
                                  context)

            db.flush()

        return UpdateDiscussionPhase(discussion_phase=phase)
Exemple #14
0
 def Field(cls, *args, **kwargs):
     if using_refresh_tokens():
         cls._meta.fields["refresh_token"] = graphene.Field(graphene.String)
     cls._meta.fields["token"] = graphene.Field(graphene.String)
     return super().Field(*args, **kwargs)
Exemple #15
0
class Mutation(startwars.schema.Mutation, graphene.ObjectType):
    debug = graphene.Field(DjangoDebug, name='__debug')
Exemple #16
0
class CheckoutComplete(BaseMutation):
    order = graphene.Field(Order, description='Placed order')

    class Arguments:
        checkout_id = graphene.ID(description='Checkout ID', required=True)

    class Meta:
        description = (
            'Completes the checkout. As a result a new order is created and '
            'a payment charge is made. This action requires a successful '
            'payment before it can be performed.')

    @classmethod
    def mutate(cls, root, info, checkout_id):
        errors = []
        checkout = cls.get_node_or_error(info,
                                         checkout_id,
                                         errors,
                                         'checkout_id',
                                         only_type=Checkout)
        if checkout is None:
            return CheckoutComplete(errors=errors)

        taxes = get_taxes_for_cart(checkout, info.context.taxes)
        ready, checkout_error = ready_to_place_order(checkout, taxes,
                                                     info.context.discounts)
        if not ready:
            cls.add_error(field=None, message=checkout_error, errors=errors)
            return CheckoutComplete(errors=errors)

        try:
            order = create_order(cart=checkout,
                                 tracking_code=analytics.get_client_id(
                                     info.context),
                                 discounts=info.context.discounts,
                                 taxes=taxes)
        except InsufficientStock:
            cls.add_error(field=None,
                          message='Insufficient product stock.',
                          errors=errors)
            return CheckoutComplete(errors=errors)
        except voucher_model.NotApplicable:
            cls.add_error(field=None,
                          message='Voucher not applicable',
                          errors=errors)
            return CheckoutComplete(errors=errors)

        payment = checkout.get_last_active_payment()

        # remove cart after checkout is created
        checkout.delete()
        order.events.create(type=OrderEvents.PLACED.value)
        send_order_confirmation.delay(order.pk)
        send_manager_confirmation.delay(order.pk)
        order.events.create(type=OrderEvents.EMAIL_SENT.value,
                            parameters={
                                'email': order.get_user_current_email(),
                                'email_type': OrderEventsEmails.ORDER.value
                            })

        try:
            gateway_process_payment(payment=payment,
                                    payment_token=payment.token)
        except PaymentError as e:
            cls.add_error(errors=errors, field=None, message=str(e))
        return CheckoutComplete(order=order, errors=errors)
Exemple #17
0
class Query(startwars.schema.Query, graphene.ObjectType):
    debug = graphene.Field(DjangoDebug, name='__debug')
Exemple #18
0
class ProductVariantStocksCreate(BaseMutation):
    product_variant = graphene.Field(ProductVariant,
                                     description="Updated product variant.")

    class Arguments:
        variant_id = graphene.ID(
            required=True,
            description=
            "ID of a product variant for which stocks will be created.",
        )
        stocks = graphene.List(
            graphene.NonNull(StockInput),
            required=True,
            description="Input list of stocks to create.",
        )

    class Meta:
        description = "Creates stocks for product variant."
        permissions = (ProductPermissions.MANAGE_PRODUCTS, )
        error_type_class = BulkStockError
        error_type_field = "bulk_stock_errors"

    @classmethod
    def perform_mutation(cls, root, info, **data):
        errors = defaultdict(list)
        stocks = data["stocks"]
        variant = cls.get_node_or_error(info,
                                        data["variant_id"],
                                        only_type=ProductVariant)
        if stocks:
            warehouses = cls.clean_stocks_input(variant, stocks, errors)
            if errors:
                raise ValidationError(errors)
            create_stocks(variant, stocks, warehouses)
        return cls(product_variant=variant)

    @classmethod
    def clean_stocks_input(cls, variant, stocks_data, errors):
        warehouse_ids = [stock["warehouse"] for stock in stocks_data]
        cls.check_for_duplicates(warehouse_ids, errors)
        warehouses = cls.get_nodes_or_error(warehouse_ids,
                                            "warehouse",
                                            only_type=Warehouse)
        existing_stocks = variant.stocks.filter(
            warehouse__in=warehouses).values_list("warehouse__pk", flat=True)
        error_msg = "Stock for this warehouse already exists for this product variant."
        indexes = []
        for warehouse_pk in existing_stocks:
            warehouse_id = graphene.Node.to_global_id("Warehouse",
                                                      warehouse_pk)
            indexes.extend([
                i for i, id in enumerate(warehouse_ids) if id == warehouse_id
            ])
        cls.update_errors(errors, error_msg, "warehouse",
                          StockErrorCode.UNIQUE, indexes)

        return warehouses

    @classmethod
    def check_for_duplicates(cls, warehouse_ids, errors):
        duplicates = {
            id
            for id in warehouse_ids if warehouse_ids.count(id) > 1
        }
        error_msg = "Duplicated warehouse ID."
        indexes = []
        for duplicated_id in duplicates:
            indexes.append([
                i for i, id in enumerate(warehouse_ids) if id == duplicated_id
            ][-1])
        cls.update_errors(errors, error_msg, "warehouse",
                          StockErrorCode.UNIQUE, indexes)

    @classmethod
    def update_errors(cls, errors, msg, field, code, indexes):
        for index in indexes:
            error = ValidationError(msg, code=code, params={"index": index})
            errors[field].append(error)
Exemple #19
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 = ['order_lines', '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 #20
0
class RequestEmailChange(BaseMutation):
    user = graphene.Field(User, description="A user instance.")

    class Arguments:
        password = graphene.String(required=True, description="User password.")
        new_email = graphene.String(required=True, description="New user email.")
        redirect_url = graphene.String(
            required=True,
            description=(
                "URL of a view where users should be redirected to "
                "update the email address. URL in RFC 1808 format."
            ),
        )

    class Meta:
        description = "Request email change of the logged in user."
        error_type_class = AccountError
        error_type_field = "account_errors"

    @classmethod
    def check_permissions(cls, context):
        return context.user.is_authenticated

    @classmethod
    def perform_mutation(cls, _root, info, **data):
        user = info.context.user
        password = data["password"]
        new_email = data["new_email"]
        redirect_url = data["redirect_url"]

        if not user.check_password(password):
            raise ValidationError(
                {
                    "password": ValidationError(
                        "Password isn't valid.",
                        code=AccountErrorCode.INVALID_CREDENTIALS,
                    )
                }
            )
        if models.User.objects.filter(email=new_email).exists():
            raise ValidationError(
                {
                    "new_email": ValidationError(
                        "Email is used by other user.", code=AccountErrorCode.UNIQUE
                    )
                }
            )
        try:
            validate_storefront_url(redirect_url)
        except ValidationError as error:
            raise ValidationError(
                {"redirect_url": error}, code=AccountErrorCode.INVALID
            )
        token_payload = {
            "old_email": user.email,
            "new_email": new_email,
            "user_pk": user.pk,
        }
        token = create_token(token_payload, JWT_TTL_REQUEST_EMAIL_CHANGE)
        emails.send_user_change_email_url(redirect_url, user, new_email, token)
        return RequestEmailChange(user=user)
def PaginatedQuerySet(of_type, type_class, **kwargs):
    """
    Paginated QuerySet type with arguments used by Django's query sets.

    This type setts the following arguments on itself:

    * ``id``
    * ``page``
    * ``per_page``
    * ``search_query``
    * ``order``

    :param enable_search: Enable search query argument.
    :type enable_search: bool
    :param enable_order: Enable ordering via query argument.
    :type enable_order: bool
    """

    enable_search = kwargs.pop("enable_search", True)
    enable_order = kwargs.pop("enable_order", True)
    required = kwargs.get("required", False)
    type_name = type_class if isinstance(type_class,
                                         str) else type_class.__name__
    type_name = type_name.lstrip("Stub")

    # Check if the type is a Django model type. Do not perform the
    # check if value is lazy.
    if inspect.isclass(of_type) and not issubclass(
            of_type, graphene_django.DjangoObjectType):
        raise TypeError(
            f"{of_type} is not a subclass of DjangoObjectType and it "
            "cannot be used with QuerySetList.")

    # Enable page for Django Paginator.
    if "page" not in kwargs:
        kwargs["page"] = graphene.Argument(
            PositiveInt,
            description=_("Page of resulting objects to return."),
        )

    # Enable per_page for Django Paginator.
    if "per_page" not in kwargs:
        kwargs["per_page"] = graphene.Argument(
            PositiveInt,
            description=_("The maximum number of items to include on a page."),
        )

    # Enable ordering of the queryset
    if enable_order is True and "order" not in kwargs:
        kwargs["order"] = graphene.Argument(
            graphene.String,
            description=_("Use the Django QuerySet order_by format."))

    # If type is provided as a lazy value (e.g. using lambda), then
    # the search has to be enabled explicitly.
    if (enable_search is True and not inspect.isclass(of_type)) or (
            enable_search is True and inspect.isclass(of_type)
            and class_is_indexed(of_type._meta.model)
            and "search_query" not in kwargs):
        kwargs["search_query"] = graphene.Argument(
            graphene.String,
            description=_("Filter the results using Wagtail's search."))

    if "id" not in kwargs:
        kwargs["id"] = graphene.Argument(graphene.ID,
                                         description=_("Filter by ID"))

    class PaginatedType(BasePaginatedType):
        items = graphene.List(of_type, required=required)
        pagination = graphene.Field(PaginationType, required=required)

        class Meta:
            name = type_name + "PaginatedType"

    return graphene.Field(PaginatedType, **kwargs)
Exemple #22
0
class AttributeCreate(AttributeMixin, ModelMutation):
    # Needed by AttributeMixin,
    # represents the input name for the passed list of values
    ATTRIBUTE_VALUES_FIELD = "values"

    attribute = graphene.Field(Attribute, description="The created attribute.")

    class Arguments:
        input = AttributeCreateInput(
            required=True,
            description="Fields required to create an attribute.")

    class Meta:
        model = models.Attribute
        description = "Creates an attribute."
        error_type_class = AttributeError
        error_type_field = "attribute_errors"

    @classmethod
    def clean_input(cls, info, instance, data, input_cls=None):
        cleaned_input = super().clean_input(info, instance, data, input_cls)
        if cleaned_input.get(
                "input_type"
        ) == AttributeInputType.REFERENCE and not cleaned_input.get(
                "entity_type"):
            raise ValidationError({
                "entity_type":
                ValidationError(
                    "Entity type is required when REFERENCE input type is used.",
                    code=AttributeErrorCode.REQUIRED.value,
                )
            })
        return cleaned_input

    @classmethod
    def perform_mutation(cls, _root, info, **data):
        input = data.get("input")
        # check permissions based on attribute type
        if input["type"] == AttributeTypeEnum.PRODUCT_TYPE.value:
            permissions = (
                ProductTypePermissions.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES, )
        else:
            permissions = (
                PageTypePermissions.MANAGE_PAGE_TYPES_AND_ATTRIBUTES, )
        if not cls.check_permissions(info.context, permissions):
            raise PermissionDenied()

        instance = models.Attribute()

        # Do cleaning and uniqueness checks
        cleaned_input = cls.clean_input(info, instance, input)
        cls.clean_attribute(instance, cleaned_input)
        cls.clean_values(cleaned_input, instance)

        # Construct the attribute
        instance = cls.construct_instance(instance, cleaned_input)
        cls.clean_instance(info, instance)

        # Commit it
        instance.save()
        cls._save_m2m(info, instance, cleaned_input)

        # Return the attribute that was created
        return AttributeCreate(attribute=instance)
class FulfillmentReturnProducts(FulfillmentRefundAndReturnProductBase):
    return_fulfillment = graphene.Field(
        Fulfillment, description="A return fulfillment."
    )
    replace_fulfillment = graphene.Field(
        Fulfillment, description="A replace fulfillment."
    )
    order = graphene.Field(Order, description="Order which fulfillment was returned.")
    replace_order = graphene.Field(
        Order,
        description="A draft order which was created for products with replace flag.",
    )

    class Meta:
        description = "Return products."
        permissions = (OrderPermissions.MANAGE_ORDERS,)
        error_type_class = OrderError
        error_type_field = "order_errors"

    class Arguments:
        order = graphene.ID(
            description="ID of the order to be returned.", required=True
        )
        input = OrderReturnProductsInput(
            required=True,
            description="Fields required to return products.",
        )

    @classmethod
    def clean_input(cls, info, order_id, input):
        cleaned_input = {}
        amount_to_refund = input.get("amount_to_refund")
        include_shipping_costs = input["include_shipping_costs"]
        refund = input["refund"]

        qs = order_models.Order.objects.prefetch_related("payments")
        order = cls.get_node_or_error(
            info, order_id, field="order", only_type=Order, qs=qs
        )
        payment = order.get_last_payment()
        if refund:
            cls.clean_order_payment(payment, cleaned_input)
            cls.clean_amount_to_refund(amount_to_refund, payment, cleaned_input)

        cleaned_input.update(
            {
                "include_shipping_costs": include_shipping_costs,
                "order": order,
                "refund": refund,
            }
        )

        order_lines_data = input.get("order_lines")
        fulfillment_lines_data = input.get("fulfillment_lines")

        if order_lines_data:
            cls.clean_lines(order_lines_data, cleaned_input)
        if fulfillment_lines_data:
            cls.clean_fulfillment_lines(
                fulfillment_lines_data,
                cleaned_input,
                whitelisted_statuses=[
                    FulfillmentStatus.FULFILLED,
                    FulfillmentStatus.REFUNDED,
                ],
            )
        return cleaned_input

    @classmethod
    def perform_mutation(cls, _root, info, **data):
        cleaned_input = cls.clean_input(info, data.get("order"), data.get("input"))
        order = cleaned_input["order"]
        response = create_fulfillments_for_returned_products(
            get_user_or_app_from_context(info.context),
            order,
            cleaned_input.get("payment"),
            cleaned_input.get("order_lines", []),
            cleaned_input.get("fulfillment_lines", []),
            info.context.plugins,
            cleaned_input["refund"],
            cleaned_input.get("amount_to_refund"),
            cleaned_input["include_shipping_costs"],
        )
        return_fulfillment, replace_fulfillment, replace_order = response
        return cls(
            order=order,
            return_fulfillment=return_fulfillment,
            replace_fulfillment=replace_fulfillment,
            replace_order=replace_order,
        )
Exemple #24
0
class AttributeUpdate(AttributeMixin, ModelMutation):
    # Needed by AttributeMixin,
    # represents the input name for the passed list of values
    ATTRIBUTE_VALUES_FIELD = "add_values"

    attribute = graphene.Field(Attribute, description="The updated attribute.")

    class Arguments:
        id = graphene.ID(required=True,
                         description="ID of an attribute to update.")
        input = AttributeUpdateInput(
            required=True,
            description="Fields required to update an attribute.")

    class Meta:
        model = models.Attribute
        description = "Updates attribute."
        permissions = (
            ProductTypePermissions.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES, )
        error_type_class = AttributeError
        error_type_field = "attribute_errors"

    @classmethod
    def clean_remove_values(cls, cleaned_input, instance):
        """Check if the values to be removed are assigned to the given attribute."""
        remove_values = cleaned_input.get("remove_values", [])
        for value in remove_values:
            if value.attribute != instance:
                msg = "Value %s does not belong to this attribute." % value
                raise ValidationError({
                    "remove_values":
                    ValidationError(msg, code=AttributeErrorCode.INVALID)
                })
        return remove_values

    @classmethod
    def _save_m2m(cls, info, instance, cleaned_data):
        super()._save_m2m(info, instance, cleaned_data)
        for attribute_value in cleaned_data.get("remove_values", []):
            attribute_value.delete()

    @classmethod
    def perform_mutation(cls, _root, info, id, input):
        instance = cls.get_node_or_error(info, id, only_type=Attribute)

        # Do cleaning and uniqueness checks
        cleaned_input = cls.clean_input(info, instance, input)
        cls.clean_attribute(instance, cleaned_input)
        cls.clean_values(cleaned_input, instance)
        cls.clean_remove_values(cleaned_input, instance)

        # Construct the attribute
        instance = cls.construct_instance(instance, cleaned_input)
        cls.clean_instance(info, instance)

        # Commit it
        instance.save()
        cls._save_m2m(info, instance, cleaned_input)

        # Return the attribute that was created
        return AttributeUpdate(attribute=instance)
Exemple #25
0
 class Query(graphene.ObjectType):
     base = graphene.Field(BaseType)
     child = graphene.Field(ChildType)
Exemple #26
0
class AttributeValueCreate(AttributeMixin, ModelMutation):
    ATTRIBUTE_VALUES_FIELD = "input"

    attribute = graphene.Field(Attribute, description="The updated attribute.")

    class Arguments:
        attribute_id = graphene.ID(
            required=True,
            name="attribute",
            description="Attribute to which value will be assigned.",
        )
        input = AttributeValueCreateInput(
            required=True,
            description="Fields required to create an AttributeValue.")

    class Meta:
        model = models.AttributeValue
        description = "Creates a value for an attribute."
        permissions = (ProductPermissions.MANAGE_PRODUCTS, )
        error_type_class = AttributeError
        error_type_field = "attribute_errors"

    @classmethod
    def clean_input(cls, info, instance, data):
        cleaned_input = super().clean_input(info, instance, data)
        if "name" in cleaned_input:
            cleaned_input["slug"] = generate_unique_slug(
                instance, cleaned_input["name"])
        input_type = instance.attribute.input_type

        is_swatch_attr = input_type == AttributeInputType.SWATCH
        only_swatch_fields = ["file_url", "content_type"]
        errors = {}
        if not is_swatch_attr:
            for field in only_swatch_fields:
                if cleaned_input.get(field):
                    errors[field] = ValidationError(
                        f"The field {field} can be defined only for swatch attributes.",
                        code=AttributeErrorCode.INVALID.value,
                    )
        else:
            try:
                cls.validate_swatch_attr_value(cleaned_input)
            except ValidationError as error:
                errors["value"] = error.error_dict[cls.ATTRIBUTE_VALUES_FIELD]
                errors["fileUrl"] = error.error_dict[
                    cls.ATTRIBUTE_VALUES_FIELD]
        if errors:
            raise ValidationError(errors)

        return cleaned_input

    @classmethod
    def clean_instance(cls, info, instance):
        validate_value_is_unique(instance.attribute, instance)
        super().clean_instance(info, instance)

    @classmethod
    def perform_mutation(cls, _root, info, attribute_id, input):
        attribute = cls.get_node_or_error(info,
                                          attribute_id,
                                          only_type=Attribute)
        instance = models.AttributeValue(attribute=attribute)
        cleaned_input = cls.clean_input(info, instance, input)
        instance = cls.construct_instance(instance, cleaned_input)
        cls.clean_instance(info, instance)

        instance.save()
        cls._save_m2m(info, instance, cleaned_input)
        return AttributeValueCreate(attribute=attribute,
                                    attributeValue=instance)
Exemple #27
0
    class Query(graphene.ObjectType):
        reporter = graphene.Field(ReporterType)

        def resolve_reporter(self, info):
            return Reporter(first_name="ABA", last_name="X")
Exemple #28
0
class AttributeReorderValues(BaseMutation):
    attribute = graphene.Field(
        Attribute, description="Attribute from which values are reordered.")

    class Meta:
        description = "Reorder the values of an attribute."
        permissions = (
            ProductTypePermissions.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES, )
        error_type_class = AttributeError
        error_type_field = "attribute_errors"

    class Arguments:
        attribute_id = graphene.Argument(graphene.ID,
                                         required=True,
                                         description="ID of an attribute.")
        moves = graphene.List(
            ReorderInput,
            required=True,
            description=
            "The list of reordering operations for given attribute values.",
        )

    @classmethod
    def perform_mutation(cls, _root, info, attribute_id, moves):
        pk = cls.get_global_id_or_error(attribute_id,
                                        only_type=Attribute,
                                        field="attribute_id")

        try:
            attribute = models.Attribute.objects.prefetch_related(
                "values").get(pk=pk)
        except ObjectDoesNotExist:
            raise ValidationError({
                "attribute_id":
                ValidationError(
                    f"Couldn't resolve to an attribute: {attribute_id}",
                    code=AttributeErrorCode.NOT_FOUND,
                )
            })

        values_m2m = attribute.values
        operations = {}

        # Resolve the values
        for move_info in moves:
            value_pk = cls.get_global_id_or_error(move_info.id,
                                                  only_type=AttributeValue,
                                                  field="moves")

            try:
                m2m_info = values_m2m.get(pk=int(value_pk))
            except ObjectDoesNotExist:
                raise ValidationError({
                    "moves":
                    ValidationError(
                        f"Couldn't resolve to an attribute value: {move_info.id}",
                        code=AttributeErrorCode.NOT_FOUND,
                    )
                })
            operations[m2m_info.pk] = move_info.sort_order

        with traced_atomic_transaction():
            perform_reordering(values_m2m, operations)
        attribute.refresh_from_db(fields=["values"])
        return AttributeReorderValues(attribute=attribute)
Exemple #29
0
class ProductVariant(ChannelContextTypeWithMetadata,
                     CountableDjangoObjectType):
    channel_listings = graphene.List(
        graphene.NonNull(ProductVariantChannelListing),
        description="List of price information in channels for the product.",
    )
    pricing = graphene.Field(
        VariantPricingInfo,
        address=destination_address_argument,
        description=
        ("Lists the storefront variant's pricing, the current price and discounts, "
         "only meant for displaying."),
    )
    attributes = graphene.List(
        graphene.NonNull(SelectedAttribute),
        required=True,
        description="List of attributes assigned to this variant.",
        variant_selection=graphene.Argument(
            VariantAttributeScope,
            description="Define scope of returned attributes.",
        ),
    )
    cost_price = graphene.Field(Money,
                                description="Cost price of the variant.")
    margin = graphene.Int(description="Gross margin percentage value.")
    quantity_ordered = graphene.Int(description="Total quantity ordered.")
    revenue = graphene.Field(
        TaxedMoney,
        period=graphene.Argument(ReportingPeriod),
        description=
        ("Total revenue generated by a variant in given period of time. Note: this "
         "field should be queried using `reportProductSales` query as it uses "
         "optimizations suitable for such calculations."),
    )
    images = graphene.List(
        lambda: ProductImage,
        description="List of images for the product variant.",
        deprecation_reason=
        "Will be removed in Saleor 4.0. Use the `media` instead.",
    )
    media = graphene.List(
        graphene.NonNull(lambda: ProductMedia),
        description="List of media for the product variant.",
    )
    translation = TranslationField(
        ProductVariantTranslation,
        type_name="product variant",
        resolver=ChannelContextType.resolve_translation,
    )
    digital_content = graphene.Field(
        DigitalContent, description="Digital content for the product variant.")
    stocks = graphene.Field(
        graphene.List(Stock),
        description="Stocks for the product variant.",
        address=destination_address_argument,
        country_code=graphene.Argument(
            CountryCodeEnum,
            description=
            ("DEPRECATED: use `address` argument instead. This argument will be "
             "removed in Saleor 4.0. Two-letter ISO 3166-1 country code."),
        ),
    )
    quantity_available = graphene.Int(
        required=True,
        description="Quantity of a product available for sale in one checkout.",
        address=destination_address_argument,
        country_code=graphene.Argument(
            CountryCodeEnum,
            description=
            ("DEPRECATED: use `address` argument instead. This argument will be "
             "removed in Saleor 4.0."
             "Two-letter ISO 3166-1 country code. When provided, the exact quantity "
             "from a warehouse operating in shipping zones that contain this "
             "country will be returned. Otherwise, it will return the maximum "
             "quantity from all shipping zones."),
        ),
    )

    class Meta:
        default_resolver = ChannelContextType.resolver_with_context
        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
    @one_of_permissions_required(
        [ProductPermissions.MANAGE_PRODUCTS, OrderPermissions.MANAGE_ORDERS])
    def resolve_stocks(
        root: ChannelContext[models.ProductVariant],
        info,
        address=None,
        country_code=None,
    ):
        if address is not None:
            country_code = get_user_country_context(
                address, info.context.site.settings.company_address)

        return StocksWithAvailableQuantityByProductVariantIdAndCountryCodeLoader(
            info.context).load((root.node.id, country_code))

    @staticmethod
    def resolve_quantity_available(
        root: ChannelContext[models.ProductVariant],
        info,
        address=None,
        country_code=None,
    ):
        if address is not None:
            country_code = get_user_country_context(
                address, info.context.site.settings.company_address)

        if not root.node.track_inventory:
            return settings.MAX_CHECKOUT_LINE_QUANTITY

        return AvailableQuantityByProductVariantIdAndCountryCodeLoader(
            info.context).load((root.node.id, country_code))

    @staticmethod
    @permission_required(ProductPermissions.MANAGE_PRODUCTS)
    def resolve_digital_content(root: ChannelContext[models.ProductVariant],
                                *_args):
        return getattr(root.node, "digital_content", None)

    @staticmethod
    def resolve_attributes(
        root: ChannelContext[models.ProductVariant],
        info,
        variant_selection: Optional[str] = None,
    ):
        def apply_variant_selection_filter(selected_attributes):
            if not variant_selection or variant_selection == VariantAttributeScope.ALL:
                return selected_attributes
            attributes = [
                selected_att["attribute"]
                for selected_att in selected_attributes
            ]
            variant_selection_attrs = get_variant_selection_attributes(
                attributes)

            if variant_selection == VariantAttributeScope.VARIANT_SELECTION:
                return [
                    selected_attribute
                    for selected_attribute in selected_attributes if
                    selected_attribute["attribute"] in variant_selection_attrs
                ]
            return [
                selected_attribute
                for selected_attribute in selected_attributes if
                selected_attribute["attribute"] not in variant_selection_attrs
            ]

        return (SelectedAttributesByProductVariantIdLoader(info.context).load(
            root.node.id).then(apply_variant_selection_filter))

    @staticmethod
    @staff_member_or_app_required
    def resolve_channel_listings(root: ChannelContext[models.ProductVariant],
                                 info, **_kwargs):
        return VariantChannelListingByVariantIdLoader(info.context).load(
            root.node.id)

    @staticmethod
    def resolve_pricing(root: ChannelContext[models.ProductVariant],
                        info,
                        address=None):
        if not root.channel_slug:
            return None

        country_code = get_user_country_context(
            address, info.context.site.settings.company_address)

        channel_slug = str(root.channel_slug)
        context = info.context

        product = ProductByIdLoader(context).load(root.node.product_id)
        product_channel_listing = ProductChannelListingByProductIdAndChannelSlugLoader(
            context).load((root.node.product_id, channel_slug))
        variant_channel_listing = VariantChannelListingByVariantIdAndChannelSlugLoader(
            context).load((root.node.id, channel_slug))
        collections = CollectionsByProductIdLoader(context).load(
            root.node.product_id)
        channel = ChannelBySlugLoader(context).load(channel_slug)

        def calculate_pricing_info(discounts):
            def calculate_pricing_with_channel(channel):
                def calculate_pricing_with_product_variant_channel_listings(
                    variant_channel_listing, ):
                    def calculate_pricing_with_product(product):
                        def calculate_pricing_with_product_channel_listings(
                            product_channel_listing, ):
                            def calculate_pricing_with_collections(
                                    collections):
                                if (not variant_channel_listing
                                        or not product_channel_listing):
                                    return None
                                availability = get_variant_availability(
                                    variant=root.node,
                                    variant_channel_listing=
                                    variant_channel_listing,
                                    product=product,
                                    product_channel_listing=
                                    product_channel_listing,
                                    collections=collections,
                                    discounts=discounts,
                                    channel=channel,
                                    country=Country(country_code),
                                    local_currency=get_currency_for_country(
                                        country_code),
                                    plugins=context.plugins,
                                )
                                return VariantPricingInfo(
                                    **asdict(availability))

                            return collections.then(
                                calculate_pricing_with_collections)

                        return product_channel_listing.then(
                            calculate_pricing_with_product_channel_listings)

                    return product.then(calculate_pricing_with_product)

                return variant_channel_listing.then(
                    calculate_pricing_with_product_variant_channel_listings)

            return channel.then(calculate_pricing_with_channel)

        return (DiscountsByDateTimeLoader(context).load(
            info.context.request_time).then(calculate_pricing_info))

    @staticmethod
    def resolve_product(root: ChannelContext[models.ProductVariant], info):
        product = ProductByIdLoader(info.context).load(root.node.product_id)
        return product.then(lambda product: ChannelContext(
            node=product, channel_slug=root.channel_slug))

    @staticmethod
    @permission_required(ProductPermissions.MANAGE_PRODUCTS)
    def resolve_quantity_ordered(root: ChannelContext[models.ProductVariant],
                                 *_args):
        # This field is added through annotation when using the
        # `resolve_report_product_sales` resolver.
        return getattr(root.node, "quantity_ordered", None)

    @staticmethod
    @permission_required(ProductPermissions.MANAGE_PRODUCTS)
    def resolve_revenue(root: ChannelContext[models.ProductVariant], info,
                        period):
        start_date = reporting_period_to_date(period)
        variant = root.node
        channel_slug = root.channel_slug

        def calculate_revenue_with_channel(channel):
            if not channel:
                return None

            def calculate_revenue_with_order_lines(order_lines):
                def calculate_revenue_with_orders(orders):
                    orders_dict = {order.id: order for order in orders}
                    return calculate_revenue_for_variant(
                        variant,
                        start_date,
                        order_lines,
                        orders_dict,
                        channel.currency_code,
                    )

                order_ids = [order_line.order_id for order_line in order_lines]
                return (OrderByIdLoader(info.context).load_many(
                    order_ids).then(calculate_revenue_with_orders))

            return (OrderLinesByVariantIdAndChannelIdLoader(info.context).load(
                (variant.id,
                 channel.id)).then(calculate_revenue_with_order_lines))

        return (ChannelBySlugLoader(info.context).load(channel_slug).then(
            calculate_revenue_with_channel))

    @staticmethod
    def resolve_media(root: ChannelContext[models.ProductVariant], info,
                      *_args):
        return MediaByProductVariantIdLoader(info.context).load(root.node.id)

    @staticmethod
    def resolve_images(root: ChannelContext[models.ProductVariant], info,
                       *_args):
        return ImagesByProductVariantIdLoader(info.context).load(root.node.id)

    @staticmethod
    def __resolve_reference(root: ChannelContext[models.ProductVariant], _info,
                            **_kwargs):
        return graphene.Node.get_node_from_global_id(_info, root.node.id)

    @staticmethod
    def resolve_weight(root: ChannelContext[models.ProductVariant], _info,
                       **_kwargs):
        return convert_weight_to_default_weight_unit(root.node.weight)
Exemple #30
0
class Category(CountableDjangoObjectType):
    ancestors = PrefetchingConnectionField(
        lambda: Category, description="List of ancestors of the category.")
    products = PrefetchingConnectionField(
        Product, description="List of products in the category.")
    url = graphene.String(
        description="The storefront's URL for the category.",
        deprecation_reason="This field will be removed after 2020-07-31.",
    )
    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 = TranslationField(CategoryTranslation, type_name="category")

    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, ObjectWithMetadata]
        model = models.Category

    @staticmethod
    def resolve_ancestors(root: models.Category, info, **_kwargs):
        return root.get_ancestors()

    @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):
        return root.children.all()

    @staticmethod
    def resolve_url(root: models.Category, _info):
        return ""

    @staticmethod
    def resolve_products(root: models.Category, info, **_kwargs):
        requestor = get_user_or_app_from_context(info.context)
        tree = root.get_descendants(include_self=True)
        qs = models.Product.objects.published()
        if not qs.user_has_access_to_all(requestor):
            qs = qs.exclude(visible_in_listings=False)
        return qs.filter(category__in=tree)

    @staticmethod
    @permission_required(ProductPermissions.MANAGE_PRODUCTS)
    def resolve_private_meta(root: models.Category, _info):
        return resolve_private_meta(root, _info)

    @staticmethod
    def resolve_meta(root: models.Category, _info):
        return resolve_meta(root, _info)

    @staticmethod
    def __resolve_reference(root, _info, **_kwargs):
        return graphene.Node.get_node_from_global_id(_info, root.id)