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))
class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) def resolve_reporter(self, info): return SimpleLazyObject( lambda: SimpleLazyObject(lambda: Reporter(id=1)))
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)
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)
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
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()
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)
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()
class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) debug = graphene.Field(DjangoDebug, name="__debug") def resolve_reporter(self, info, **args): return Reporter.objects.first()
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)
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)
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)
class Mutation(startwars.schema.Mutation, graphene.ObjectType): debug = graphene.Field(DjangoDebug, name='__debug')
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)
class Query(startwars.schema.Query, graphene.ObjectType): debug = graphene.Field(DjangoDebug, name='__debug')
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)
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()
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)
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, )
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)
class Query(graphene.ObjectType): base = graphene.Field(BaseType) child = graphene.Field(ChildType)
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)
class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) def resolve_reporter(self, info): return Reporter(first_name="ABA", last_name="X")
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)
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)
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)