def mutate_and_get_payload(cls, root, info, **input): order_number = input.get("order_number", None) try: order = Order.objects.get(order_number=order_number) if order.status != OrderStatus.OFFERED: raise VenepaikkaGraphQLError( _("The order is not valid anymore") + f": {OrderStatus(order.status).label}") application = order.lease.application if order.lease else None if (isinstance(application, WinterStorageApplication) and application.area_type == ApplicationAreaType.UNMARKED): raise VenepaikkaGraphQLError( _("Cannot cancel Unmarked winter storage order")) order.set_status(OrderStatus.REJECTED, _("Order rejected by customer")) order.invalidate_tokens() # annotations aren’t reloaded if using refresh_from_db, so use get() since we need # to have the updated value of rejected_at in the notice order = Order.objects.get(pk=order.pk) send_cancellation_notice(order) except ( Order.DoesNotExist, ValidationError, AnymailError, OSError, ) as e: raise VenepaikkaGraphQLError(e) return CancelOrderMutation()
def mutate_and_get_payload(cls, root, info, **input): pier = get_node_from_global_id( info, input.pop("id"), only_type=PierNode, nullable=False, ) if input.get("harbor_id"): harbor = get_node_from_global_id( info, input.pop("harbor_id"), only_type=HarborNode, nullable=False, ) input["harbor"] = harbor boat_types = set() for boat_type_id in input.pop("suitable_boat_types", []): try: boat_type = BoatType.objects.get(pk=boat_type_id) except BoatType.DoesNotExist as e: raise VenepaikkaGraphQLError(e) boat_types.add(boat_type) try: update_object(pier, input) pier.suitable_boat_types.set(boat_types) except IntegrityError as e: raise VenepaikkaGraphQLError(e) return UpdatePierMutation(pier=pier)
def mutate_and_get_payload(cls, root, info, application_id, new_berth_id, **input): try: application = get_node_from_global_id( info, application_id, only_type=BerthApplicationNode, nullable=False) if not application.customer: raise VenepaikkaGraphQLError( _("Application must be connected to a customer")) if not application.berth_switch: raise VenepaikkaGraphQLError( _("Application must be a switch application")) new_berth = get_node_from_global_id(info, new_berth_id, only_type=BerthNode, nullable=False) if old_lease_id := input.get("old_lease_id"): old_lease = get_node_from_global_id(info, old_lease_id, only_type=BerthLeaseNode, nullable=False) else:
def mutate_and_get_payload(cls, root, info, **input): profile = get_node_from_global_id(info, input.pop("id"), only_type=ProfileNode, nullable=False) delete_organization = input.pop("delete_organization", False) if delete_organization: if "organization" in input: raise VenepaikkaGraphQLError( _("You cannot pass deleteOrganization: true and organization input" )) if not profile.organization: raise VenepaikkaGraphQLError( _("The passed Profile is not associated with an Organization" )) profile.organization.delete() profile.refresh_from_db() elif "organization" in input: Organization.objects.update_or_create( customer=profile, defaults=input.pop("organization")) profile.refresh_from_db() try: update_object(profile, input) except ValidationError as e: # Flatten all the error messages on a single list errors = sum(e.message_dict.values(), []) raise VenepaikkaGraphQLError(errors) return UpdateBerthServicesProfileMutation(profile=profile)
def mutate_and_get_payload(cls, root, info, **input): try: order = Order.objects.get(order_number=input.get("order_number")) except Order.DoesNotExist: raise VenepaikkaGraphQLError(_("No order found for given order number")) try: contract = order.lease.contract except Exception: raise VenepaikkaGraphQLError(_("No contract found for given order")) try: URLValidator()(input.get("return_url")) except ValidationError: raise VenepaikkaGraphQLError(_("return_url is not a valid URL")) auth_method_identifiers = map( lambda auth_method: auth_method["identifier"], get_contract_service().get_auth_methods(), ) if input.get("auth_service") not in auth_method_identifiers: raise VenepaikkaGraphQLError( _("auth_service is not a supported authentication service") ) signing_url = get_contract_service().fulfill_contract( contract, input.get("auth_service"), input.get("return_url"), ) return FulfillContractMutation(signing_url=signing_url)
def mutate_and_get_payload(cls, root, info, **input): if input.get("customer_id") and input.get("lease_id"): raise VenepaikkaGraphQLError( _("Cannot receive both Lease and Customer")) # TODO: until the price input is added, the customer input won't have an effect # _customer = input.pop("customer_id", None) input.pop("customer_id", None) if lease_id := input.pop("lease_id", None): lease = None try: lease = get_node_from_global_id(info, lease_id, BerthLeaseNode, nullable=True) # If a different node type is received get_node raises an assertion error # when trying to validate the type except AssertionError: lease = get_node_from_global_id(info, lease_id, WinterStorageLeaseNode, nullable=True) finally: if not lease: raise VenepaikkaGraphQLError( _("Lease with the given ID does not exist")) input["lease"] = lease input["customer"] = lease.customer
def validate_application_status(cls, application, info, input): if application.status != ApplicationStatus.PENDING: if is_customer(info.context.user): raise VenepaikkaGraphQLError( _("Cannot modify the application once it has been processed" )) # If the input receives explicitly customerId: None if "customer_id" in input and input.get("customer_id") is None: raise VenepaikkaGraphQLError( _("Customer cannot be disconnected from processed applications" ))
def remove_boat_certificates(certificates, boat): try: for cert_id in certificates: cert_id = from_global_id(cert_id) BoatCertificate.objects.get(pk=cert_id, boat=boat).delete() except BoatCertificate.DoesNotExist as e: raise VenepaikkaGraphQLError(e)
def resolve_berths(self, info, harbor: GlobalID = None, pier: GlobalID = None, min_width: Decimal = 0, min_length: Decimal = 0, **kwargs): if pier and harbor: raise VenepaikkaGraphQLError( _("Cannot pass both pier and harbor filters")) filters = Q() if harbor: filters &= Q(pier__harbor_id=from_global_id(harbor, HarborNode)) if pier: filters &= Q(pier_id=from_global_id(pier, PierNode)) if min_width: filters &= Q(berth_type__width__gte=min_width) if min_length: filters &= Q(berth_type__length__gte=min_length) if "is_available" in kwargs: filters &= Q(is_available=kwargs.get("is_available")) if "is_invoiceable" in kwargs: filters &= Q(is_invoiceable=kwargs.get("is_invoiceable")) return info.context.berth_loader.load_many( keys=Berth.objects.filter(filters).values_list("id", flat=True))
def mutate_and_get_payload(cls, root, info, offer_number, is_accepted, **input): offer = BerthSwitchOffer.objects.get(offer_number=offer_number) if is_accepted: if offer.status == OfferStatus.ACCEPTED: # do not proceed with db updates in exchange_berth_for_lease raise VenepaikkaGraphQLError(_("Offer is already accepted")) offer.set_status(OfferStatus.ACCEPTED) old_lease, new_lease = exchange_berth_for_lease( old_lease=offer.lease, new_berth=offer.berth, switch_date=max(today().date(), offer.lease.start_date), old_lease_comment=_( "Lease terminated due to a berth switch offer"), new_lease_comment=_("Lease created from a berth switch offer"), ) new_lease.application = offer.application new_lease.save() if old_lease.order: new_lease.orders.add(old_lease.order) else: # Reject offer offer.set_status(OfferStatus.REJECTED) return AcceptBerthSwitchOfferMutation()
def mutate_and_get_payload(cls, root, info, **input): order_number = input.get("order_number", None) try: order = Order.objects.get(order_number=order_number) if order.status not in (OrderStatus.OFFERED, OrderStatus.REJECTED): raise VenepaikkaGraphQLError( _("The order is not valid anymore") + f": {OrderStatus(order.status).label}") payment_url = get_payment_provider( info.context, ui_return_url=settings.VENE_UI_RETURN_URL).initiate_payment( order) except Order.DoesNotExist as e: raise VenepaikkaGraphQLError(e) return ConfirmPaymentMutation(url=payment_url)
def add_map_files(model, map_files, instance): try: for map_file in map_files: map_instance = model(map_file=map_file) instance.maps.add(map_instance, bulk=False) except IntegrityError as e: raise VenepaikkaGraphQLError(e)
def mutate_and_get_payload(cls, root, info, order_id, **input): profile_token = input.get("profile_token", None) try: order = get_node_from_global_id(info, order_id, OrderNode, nullable=False) refund = get_payment_provider(info.context).initiate_refund(order) if profile_token: # order.customer_email and order.customer_phone could be stale, if contact # info in profile service has been changed. profile = fetch_order_profile(order, profile_token) update_order_from_profile(order, profile) send_refund_notice(order) except ( AnymailError, OSError, Order.DoesNotExist, ValidationError, VenepaikkaGraphQLError, VenepaikkaPaymentError, ) as e: # If there's an error with either the validated data or the VismaPay service raise VenepaikkaGraphQLError(str(e)) from e return RefundOrderMutation(order_refund=refund)
def mutate_and_get_payload(cls, root, info, **input): from customers.schema import ProfileNode application = get_node_from_global_id( info, input.pop("id"), only_type=WinterStorageApplicationNode) cls.validate_application_status(application, info, input) # This allows for getting explicit None values if "customer_id" in input: customer_id = input.pop("customer_id", None) if is_customer(info.context.user): raise VenepaikkaGraphQLError( _("A customer cannot modify the customer connected to the application" )) input["customer"] = get_node_from_global_id(info, customer_id, only_type=ProfileNode, nullable=True) if remove_choices := input.pop("remove_choices", []): for choice_id in remove_choices: try: choice = WinterStorageAreaChoice.objects.get( id=from_global_id( choice_id, node_type=WinterStorageAreaChoiceType)) choice.delete() except WinterStorageAreaChoice.DoesNotExist: pass
def return_queryset_if_user_has_permissions(queryset, user, *models, customer_queryset=None): """ Checks whether the user has permissions to access the queryset. 1. If the passed queryset is falsy, we return None. 2. If the user making the request is a customer, the elements on the queryset belonging to it are returned. 4. If the user is not a customer, but the user has permissions to view the necessary models, the whole queryset is returned. 5. If the user has no permissions to view it, an error is raised. :param queryset: Django QuerySet being accessed :param user: the user from the current request / session :param models: Django models that user has to have permissions to :param customer_queryset: [Optional] If the customer property is nested on the model, a custom filtered queryset can be passed :return: None | Django QuerySet | Exception raised """ from users.utils import is_customer, user_has_view_permission if is_customer(user): if customer_queryset: return customer_queryset return queryset.filter(customer__user=user) if user_has_view_permission(*models)(user): return queryset raise VenepaikkaGraphQLError( _("You do not have permission to perform this action."))
def return_node_if_user_has_permissions(node, user, *models): """ Checks whether the user has permissions to access this node / model. 1. If the passed node is falsy, we return None. 2. Otherwise, try to get the user this node belongs to (get_node_user) 3. If the node belongs to the user making the request, the node / model is returned. 4. If the node does not belong to the user, but the user has permissions to view the necessary models, the node / model is still returned. 5. If the node does not belong to the user and the user has no permissions to view it, an error is raised. :param node: Graphene Node or Django Model being accessed :param user: the user from the current request / session :param models: Django models that user has to have permissions to :return: None | Django Model or Graphene Node | Exception raised """ from users.utils import user_has_view_permission if not node: return None node_user = get_node_user(node) if (node_user and node_user == user) or user_has_view_permission(*models)(user): return node else: raise VenepaikkaGraphQLError( _("You do not have permission to perform this action."))
def validate_switch_date(cls, switch_date): if isinstance(switch_date, datetime): switch_date = switch_date.date() if switch_date < date.today() - relativedelta(months=6): raise VenepaikkaGraphQLError( _("Switch date is more than 6 months in the past") )
def mutate_and_get_payload(cls, root, info, **input): try: product = BerthProduct.objects.create(**input) except (ValidationError, IntegrityError) as e: raise VenepaikkaGraphQLError(e) return CreateBerthProductMutation(berth_product=product)
def validate_availability_level_id(cls, input): if availability_level_id := input.pop("availability_level_id", None): try: input["availability_level"] = AvailabilityLevel.objects.get( pk=availability_level_id ) except AvailabilityLevel.DoesNotExist as e: raise VenepaikkaGraphQLError(e)
def mutate_and_get_payload(cls, root, info, **input): lease = get_node_from_global_id( info, input.pop("lease_id"), only_type=WinterStorageLeaseNode, nullable=False, ) if lease.status != LeaseStatus.PAID: raise VenepaikkaGraphQLError(_("Lease must be in PAID status")) elif lease.application.area_type != ApplicationAreaType.UNMARKED: raise VenepaikkaGraphQLError(_("Lease must refer to unmarked area")) new_sticker_number = get_next_sticker_number(lease.start_date) lease.sticker_number = new_sticker_number lease.sticker_posted = None lease.save() return AssignNewStickerNumberMutation(sticker_number=new_sticker_number)
def mutate_and_get_payload(cls, root, info, orders, **input): successful_orders = [] failed_orders = [] for order_input in orders: order_id = order_input.pop("id") try: with transaction.atomic(): order = get_node_from_global_id(info, order_id, only_type=OrderNode, nullable=False) if lease_id := order_input.pop("lease_id", None): lease = None try: lease = get_node_from_global_id(info, lease_id, BerthLeaseNode, nullable=True) # If a different node type is received get_node raises an assertion error # when trying to validate the type except AssertionError: lease = get_node_from_global_id( info, lease_id, WinterStorageLeaseNode, nullable=True) finally: if not lease: raise VenepaikkaGraphQLError( _("Lease with the given ID does not exist") ) order_input["lease"] = lease # handle case where order_input has both lease and status. # set order status only after changing the lease, because setting order status # usually triggers a change in lease status. new_status = order_input.pop("status", None) update_object(order, order_input) if new_status: order.set_status(new_status, _("Manually updated by admin")) except ( ValidationError, IntegrityError, VenepaikkaGraphQLError, OrderStatusTransitionError, ) as e: failed_orders.append(FailedOrderType(id=order_id, error=str(e))) else: successful_orders.append(order) return UpdateOrdersMutation(successful_orders=successful_orders, failed_orders=failed_orders)
def lookup_application_and_customer(cls, info, input): from customers.schema import ProfileNode # import here avoid circular import if application_id := input.pop("application_id", None): if "customer_id" in input: raise VenepaikkaGraphQLError( _( "Can not specify both application and customer when creating a new berth lease" ) ) application: BerthApplication = get_node_from_global_id( info, application_id, only_type=BerthApplicationNode, nullable=False, ) if not application.customer: raise VenepaikkaGraphQLError( _("Application must be connected to an existing customer first") ) input["application"] = application input["customer"] = application.customer
def mutate_and_get_payload(cls, root, info, **input): winter_storage_place_type = get_node_from_global_id( info, input.get("id"), only_type=WinterStoragePlaceTypeNode, nullable=False, ) if place_count := winter_storage_place_type.places.count(): raise VenepaikkaGraphQLError( _( f"Cannot delete WinterStoragePlaceType because it has {place_count} related places" ) )
def mutate_and_get_payload(cls, root, info, **input): product = get_node_from_global_id(info, input.pop("id"), only_type=AdditionalProductNode, nullable=False) try: update_object(product, input) except (ValidationError, IntegrityError) as e: raise VenepaikkaGraphQLError(e) return UpdateAdditionalProductMutation(additional_product=product)
def mutate_and_get_payload(cls, root, info, **input): if AdditionalProduct.objects.filter( service=input.get("service"), period=input.get("period")).exists(): raise VenepaikkaGraphQLWarning( _("Additional product already exists")) try: product = AdditionalProduct.objects.create(**input) except (ValidationError, IntegrityError) as e: raise VenepaikkaGraphQLError(e) return CreateAdditionalProductMutation(additional_product=product)
def delete_inactive_leases(lease_class, lookup, model_name): # Get all the leases related to the resource leases = lease_class.objects.filter(lookup) # If there's any active lease, raise an error if leases.filter(status=LeaseStatus.PAID).count() > 0: raise VenepaikkaGraphQLError( _(f"Cannot delete {model_name} because it has some related leases") ) # Delete all the leases associated to the Harbor leases.delete()
def mutate_and_get_payload(cls, root, info, **input): lease = get_node_from_global_id( info, input.pop("id"), only_type=WinterStorageLeaseNode, nullable=False, ) application_id = input.get("application_id") if application_id: # If the application id was passed, raise an error if it doesn't exist application = get_node_from_global_id( info, application_id, only_type=WinterStorageApplicationNode, nullable=False, ) if not application.customer: raise VenepaikkaGraphQLError( _("Application must be connected to an existing customer first") ) input["application"] = application input["customer"] = application.customer if input.get("boat_id", False): from customers.schema import BoatNode boat = get_node_from_global_id( info, input.pop("boat_id"), only_type=BoatNode, nullable=False, ) if boat.owner.id != input["customer"].id: raise VenepaikkaGraphQLError( _("Boat does not belong to the same customer as the Application") ) input["boat"] = boat try: update_object(lease, input) except ValidationError as e: raise VenepaikkaGraphQLError(str(e)) return UpdateWinterStorageLeaseMutation(winter_storage_lease=lease)
def exchange_berth_for_lease( old_lease: BerthLease, new_berth: Berth, switch_date: date, old_lease_comment: str, new_lease_comment: str, ) -> (BerthLease, BerthLease): # Avoid circular imports from leases.models import BerthLease end_date = old_lease.end_date contract = None if hasattr(old_lease, "contract"): contract = old_lease.contract old_lease = terminate_lease( lease=old_lease, end_date=switch_date, send_notice=False, ) if len(old_lease.comment) > 0: old_lease.comment += f"\n{old_lease_comment}" else: old_lease.comment = old_lease_comment old_lease.save(update_fields=["comment"]) try: new_lease = BerthLease.objects.create( berth=new_berth, boat=old_lease.boat, customer=old_lease.customer, status=LeaseStatus.PAID, start_date=switch_date, end_date=end_date, comment=_( f"{new_lease_comment}\n" f"{_('Previous berth info')}:\n" f"{_('Harbor name')}: {old_lease.berth.pier.harbor.name}\n" f"{_('Pier ID')}: {old_lease.berth.pier.identifier}\n" f"{_('Berth number')}: {old_lease.berth.number}\n" ), ) if contract: contract.lease = new_lease contract.save() old_lease.refresh_from_db() new_lease.refresh_from_db() except ValidationError as e: # Flatten all the error messages on a single list errors = sum(e.message_dict.values(), []) raise VenepaikkaGraphQLError(errors) return old_lease, new_lease
def mutate_and_get_payload(cls, root, info, **input): order_line = get_node_from_global_id(info, input.pop("id"), only_type=OrderLineNode, nullable=False) try: update_object(order_line, input) except (ValidationError, IntegrityError) as e: raise VenepaikkaGraphQLError(e) return UpdateOrderLineMutation(order_line=order_line, order=order_line.order)
def update_boat_certificates(certificates, info): try: for cert_input in certificates: cert = get_node_from_global_id( info, cert_input.pop("id"), only_type=BoatCertificateNode, nullable=False, ) update_object(cert, cert_input) except IntegrityError as e: raise VenepaikkaGraphQLError(e)