Beispiel #1
0
def create_stock(
    offer: models.Offer,
    price: float,
    quantity: int = None,
    beginning: datetime.datetime = None,
    booking_limit_datetime: datetime.datetime = None,
) -> Stock:
    """Return the new stock or raise an exception if it's not possible."""
    validation.check_required_dates_for_stock(offer, beginning, booking_limit_datetime)
    validation.check_offer_is_editable(offer)
    validation.check_stocks_are_editable_for_offer(offer)

    stock = models.Stock(
        offer=offer,
        price=price,
        quantity=quantity,
        beginningDatetime=beginning,
        bookingLimitDatetime=booking_limit_datetime,
    )

    repository.save(stock)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=offer.id)

    return stock
Beispiel #2
0
def create_mediation(
    user: User,
    offer: Offer,
    credit: str,
    image_as_bytes: bytes,
    crop_params=None,
) -> Mediation:
    validation.check_mediation_thumb_quality(image_as_bytes)

    mediation = Mediation(
        author=user,
        offer=offer,
        credit=credit,
    )
    # `create_thumb()` requires the object to have an id, so we must save now.
    repository.save(mediation)

    create_thumb(mediation, image_as_bytes, image_index=0, crop_params=crop_params)
    mediation.thumbCount = 1
    repository.save(mediation)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=offer.id)

    return mediation
Beispiel #3
0
def synchronize_fnac_stocks(app) -> None:
    if not feature_queries.is_active(FeatureToggle.FNAC_SYNCHRONIZATION_V2):
        fnac_stocks_provider_id = get_provider_by_local_class("FnacStocks").id
        synchronize_venue_providers_for_provider(fnac_stocks_provider_id)
        return

    synchronize_fnac_venues_stocks()
Beispiel #4
0
def cancel_booking_by_beneficiary(user: User, booking: Booking) -> None:
    if not user.isBeneficiary:
        raise RuntimeError(
            "Unexpected call to cancel_booking_by_beneficiary with non-beneficiary user %s"
            % user)
    validation.check_beneficiary_can_cancel_booking(user, booking)

    booking.isCancelled = True
    booking.cancellationReason = BookingCancellationReasons.BENEFICIARY
    repository.save(booking)

    notifier = MailjetNotificationService()
    notifier.send_booking_cancellation_emails_to_user_and_offerer(
        booking=booking,
        reason=booking.cancellationReason,
        # FIXME: we should not have to pass this argument.
        # Notification-related code should be reorganized.
        send_email=send_raw_email,
    )

    # FIXME: why do we do that when the booking is cancelled by the
    # *beneficiary*, but not when it's cancelled by the *offerer* (see
    # cancel_booking_by_offerer)?
    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client,
                           offer_id=booking.stock.offerId)
Beispiel #5
0
def create_deposit(beneficiary: User,
                   deposit_source: str,
                   version: int = None) -> Deposit:
    """Create a new deposit for the user if there is no deposit yet.

    The ``version`` argument MUST NOT be used outside (very specific) tests.
    """
    existing_deposits = bool(
        Deposit.query.filter_by(userId=beneficiary.id).count())
    if existing_deposits:
        raise exceptions.AlreadyActivatedException(
            {"user": ["Cet utilisateur a déjà crédité son pass Culture"]})

    if version is None:
        version = 2 if feature_queries.is_active(
            FeatureToggle.APPLY_BOOKING_LIMITS_V2) else 1
    booking_configuration = bookings_conf.LIMIT_CONFIGURATIONS[version]

    deposit = Deposit(
        version=version,
        amount=booking_configuration.TOTAL_CAP,
        source=deposit_source,
        user=beneficiary,
        expirationDate=_get_expiration_date(),
    )
    return deposit
Beispiel #6
0
def delete_stock(stock: Stock) -> None:
    validation.check_stock_is_deletable(stock)

    stock.isSoftDeleted = True

    cancelled_bookings = []
    for booking in stock.bookings:
        if not booking.isCancelled and not booking.isUsed:
            booking.isCancelled = True
            cancelled_bookings.append(booking)

    repository.save(stock, *cancelled_bookings)

    if cancelled_bookings:
        try:
            user_emails.send_batch_cancellation_emails_to_users(cancelled_bookings, mailing.send_raw_email)
        except mailing.MailServiceException as exc:
            app.logger.exception("Could not notify beneficiaries about deletion of stock=%s: %s", stock.id, exc)
        try:
            user_emails.send_offerer_bookings_recap_email_after_offerer_cancellation(
                cancelled_bookings, mailing.send_raw_email
            )
        except mailing.MailServiceException as exc:
            app.logger.exception("Could not notify offerer about deletion of stock=%s: %s", stock.id, exc)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=stock.offerId)
Beispiel #7
0
def update_mediation(mediation: Mediation, is_active: bool) -> Mediation:
    mediation.isActive = is_active
    repository.save(mediation)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=mediation.offerId)

    return mediation
Beispiel #8
0
 def test_is_active_returns_false_when_feature_is_inactive(self):
     # Given
     feature = Feature.query.filter_by(
         name=FeatureToggle.WEBAPP_SIGNUP.name).first()
     feature.isActive = False
     repository.save(feature)
     # When / Then
     assert not is_active(FeatureToggle.WEBAPP_SIGNUP)
def update_venues_for_specific_provider(provider_id: int):
    if feature_queries.is_active(FeatureToggle.PARALLEL_SYNCHRONIZATION_OF_VENUE_PROVIDER):
        _update_venues_in_parallel(provider_id)
        return

    venue_providers_to_sync = get_active_venue_providers_for_specific_provider(provider_id)
    for venue_provider in venue_providers_to_sync:
        synchronize_venue_provider(venue_provider)
Beispiel #10
0
def create_mediation_v2(
    user: User,
    offer: Offer,
    credit: str,
    image_as_bytes: bytes,
    crop_params: tuple = None,
) -> Mediation:
    # checks image type, min dimensions
    validation.check_image(image_as_bytes)

    existing_mediations = mediation_queries.get_mediations_for_offers(
        [offer.id])

    mediation = Mediation(
        author=user,
        offer=offer,
        credit=credit,
    )
    # `create_thumb()` requires the object to have an id, so we must save now.
    repository.save(mediation)

    try:
        # TODO(fseguin): cleanup after image upload v2 launch
        create_thumb(mediation,
                     image_as_bytes,
                     image_index=0,
                     crop_params=crop_params,
                     use_v2=True)

    except Exception as exc:
        app.logger.exception(
            "An unexpected error was encountered during the thumbnail creation: %s",
            exc)
        # I could not use savepoints and rollbacks with SQLA
        repository.delete(mediation)
        raise ThumbnailStorageError

    else:
        mediation.thumbCount = 1
        repository.save(mediation)
        # cleanup former thumbnails and mediations
        for previous_mediation in existing_mediations:
            try:
                for thumb_index in range(0, mediation.thumbCount):
                    remove_thumb(previous_mediation, image_index=thumb_index)
            except Exception as exc:  # pylint: disable=broad-except
                app.logger.exception(
                    "An unexpected error was encountered during the thumbnails deletion for %s: %s",
                    mediation,
                    exc,
                )
            else:
                repository.delete(previous_mediation)

        if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
            redis.add_offer_id(client=app.redis_client, offer_id=offer.id)

        return mediation
Beispiel #11
0
def cancel_booking_by_offerer(booking: Booking) -> None:
    validation.check_offerer_can_cancel_booking(booking)
    booking.isCancelled = True
    booking.cancellationReason = BookingCancellationReasons.OFFERER
    repository.save(booking)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client,
                           offer_id=booking.stock.offerId)
Beispiel #12
0
        def wrapper(*args, **kwargs):
            if feature_queries.is_active(feature_toggle):
                return f(*args, **kwargs)

            errors = ApiErrors()
            errors.add_error("Forbidden",
                             "You don't have access to this page or resource")
            errors.status_code = 403
            raise errors
Beispiel #13
0
def book_offer(
    beneficiary: User,
    stock: Stock,
    quantity: int,
) -> Booking:
    """Return a booking or raise an exception if it's not possible."""
    validation.check_can_book_free_offer(beneficiary, stock)
    validation.check_offer_already_booked(beneficiary, stock.offer)
    validation.check_quantity(stock.offer, quantity)
    validation.check_stock_is_bookable(stock)
    total_amount = quantity * stock.price
    validation.check_expenses_limits(beneficiary, total_amount, stock.offer)

    # FIXME (dbaty, 2020-10-20): if we directly set relations (for
    # example with `booking.user = beneficiary`) instead of foreign keys,
    # the session tries to add the object when `get_user_expenses()`
    # is called because autoflush is enabled. As such, the PostgreSQL
    # exceptions (tooManyBookings and insufficientFunds) may raise at
    # this point and will bubble up. If we want them to be caught, we
    # have to set foreign keys, so that the session is NOT autoflushed
    # in `get_user_expenses` and is only committed in `repository.save()`
    # where exceptions are caught. Since we are using flask-sqlalchemy,
    # I don't think that we should use autoflush, nor should we use
    # the `pcapi.repository.repository` module.
    booking = Booking(
        userId=beneficiary.id,
        stockId=stock.id,
        amount=stock.price,
        quantity=quantity,
        token=generate_booking_token(),
    )

    booking.dateCreated = datetime.datetime.utcnow()
    booking.confirmationDate = compute_confirmation_date(
        stock.beginningDatetime, booking.dateCreated)

    repository.save(booking)

    try:
        user_emails.send_booking_recap_emails(booking)
    except MailServiceException as error:
        logger.exception(
            "Could not send booking=%s confirmation email to offerer: %s",
            booking.id, error)
    try:
        user_emails.send_booking_confirmation_email_to_beneficiary(booking)
    except MailServiceException as error:
        logger.exception(
            "Could not send booking=%s confirmation email to beneficiary: %s",
            booking.id, error)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=stock.offerId)

    return booking
Beispiel #14
0
def update_offer(  # pylint: disable=redefined-builtin
    offer: Offer,
    bookingEmail: str = UNCHANGED,
    description: str = UNCHANGED,
    isNational: bool = UNCHANGED,
    name: str = UNCHANGED,
    extraData: dict = UNCHANGED,
    type: str = UNCHANGED,
    url: str = UNCHANGED,
    withdrawalDetails: str = UNCHANGED,
    isActive: bool = UNCHANGED,
    isDuo: bool = UNCHANGED,
    durationMinutes: int = UNCHANGED,
    mediaUrls: List[str] = UNCHANGED,
    ageMin: int = UNCHANGED,
    ageMax: int = UNCHANGED,
    conditions: str = UNCHANGED,
    venueId: str = UNCHANGED,
    productId: str = UNCHANGED,
) -> Offer:
    # fmt: off
    modifications = {
        field: new_value
        for field, new_value in locals().items()
        if field != 'offer'
        and new_value is not UNCHANGED  # has the user provided a value for this field
        and getattr(offer, field) != new_value  # is the value different from what we have on database?
    }
    # fmt: on
    if not modifications:
        return offer

    validation.check_offer_is_editable(offer)

    if offer.isFromAllocine:
        validation.check_update_only_allowed_offer_fields_for_allocine_offer(set(modifications))

    offer.populate_from_dict(modifications)
    if offer.product.owningOfferer and offer.product.owningOfferer == offer.venue.managingOfferer:
        offer.product.populate_from_dict(modifications)
        product_has_been_updated = True
    else:
        product_has_been_updated = False

    if offer.isFromAllocine:
        offer.fieldsUpdated = list(set(offer.fieldsUpdated) | set(modifications))

    repository.save(offer)
    if product_has_been_updated:
        repository.save(offer.product)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=offer.id)

    return offer
Beispiel #15
0
def activate_venue_offers(venue_id):
    venue = load_or_404(Venue, venue_id)
    check_user_has_access_to_offerer(current_user, venue.managingOffererId)
    offers = venue.offers
    activated_offers = update_is_active_status(offers, True)
    repository.save(*activated_offers)
    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_venue_id(client=app.redis_client, venue_id=venue.id)
    return jsonify([
        as_dict(offer, includes=OFFER_INCLUDES) for offer in activated_offers
    ]), 200
Beispiel #16
0
def deactivate_venue_offers(venue_id):
    venue = load_or_404(VenueSQLEntity, venue_id)
    ensure_current_user_has_rights(RightsType.editor, venue.managingOffererId)
    offers = venue.offers
    deactivated_offers = update_is_active_status(offers, False)
    repository.save(*deactivated_offers)
    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_venue_id(client=app.redis_client, venue_id=venue.id)
    return jsonify([
        as_dict(offer, includes=OFFER_INCLUDES) for offer in deactivated_offers
    ]), 200
def _reindex_offers(created_or_updated_objects):
    if not feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        return
    offer_ids = set()
    for obj in created_or_updated_objects:
        if isinstance(obj, Stock):
            offer_ids.add(obj.offerId)
        elif isinstance(obj, Offer):
            offer_ids.add(obj.id)
    for offer_id in offer_ids:
        redis.add_offer_id(client=app.redis_client, offer_id=offer_id)
Beispiel #18
0
def update_offers_active_status(query, is_active):
    # We cannot just call `query.update()` because `distinct()` may
    # already have been called on `query`.
    query_to_update = Offer.query.filter(Offer.id.in_(query.with_entities(Offer.id)))
    query_to_update.update({"isActive": is_active}, synchronize_session=False)
    db.session.commit()

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        offer_ids = {offer_id for offer_id, in query.with_entities(Offer.id)}
        for offer_id in offer_ids:
            redis.add_offer_id(client=app.redis_client, offer_id=offer_id)
Beispiel #19
0
def upsert_stocks(
    offer_id: int, stock_data_list: List[Union[StockCreationBodyModel,
                                               StockEditionBodyModel]]
) -> List[Stock]:
    stocks = []
    edited_stocks = []
    edited_stocks_previous_beginnings = {}

    offer = offer_queries.get_offer_by_id(offer_id)

    for stock_data in stock_data_list:
        if isinstance(stock_data, StockEditionBodyModel):
            stock = Stock.queryNotSoftDeleted().filter_by(
                id=stock_data.id).first_or_404()
            if stock.offerId != offer_id:
                errors = ApiErrors()
                errors.add_error(
                    "global",
                    "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information."
                )
                errors.status_code = 403
                raise errors
            edited_stocks_previous_beginnings[
                stock.id] = stock.beginningDatetime
            edited_stock = _edit_stock(
                stock,
                price=stock_data.price,
                quantity=stock_data.quantity,
                beginning=stock_data.beginning_datetime,
                booking_limit_datetime=stock_data.booking_limit_datetime,
            )
            edited_stocks.append(edited_stock)
            stocks.append(edited_stock)
        else:
            created_stock = _create_stock(
                offer=offer,
                price=stock_data.price,
                quantity=stock_data.quantity,
                beginning=stock_data.beginning_datetime,
                booking_limit_datetime=stock_data.booking_limit_datetime,
            )
            stocks.append(created_stock)

    repository.save(*stocks)

    for stock in edited_stocks:
        previous_beginning = edited_stocks_previous_beginnings[stock.id]
        if stock.beginningDatetime != previous_beginning:
            _notify_beneficiaries_upon_stock_edit(stock)
    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client, offer_id=offer.id)

    return stocks
Beispiel #20
0
 def _create(cls, model_class, *args, **kwargs):
     if "amount" in kwargs:
         raise ValueError(
             "You cannot directly set deposit amount: set version instead")
     version = kwargs.get("version")
     if not version:
         version = 2 if feature_queries.is_active(
             FeatureToggle.APPLY_BOOKING_LIMITS_V2) else 1
     amount = bookings_conf.LIMIT_CONFIGURATIONS[version].TOTAL_CAP
     kwargs["version"] = version
     kwargs["amount"] = amount
     return super()._create(model_class, *args, **kwargs)
def _run_first_synchronization(new_venue_provider: VenueProvider):
    if not feature_queries.is_active(
            FeatureToggle.SYNCHRONIZE_VENUE_PROVIDER_IN_WORKER):
        subprocess.Popen([
            "python",
            "src/pcapi/scripts/pc.py",
            "update_providables",
            "--venue-provider-id",
            str(new_venue_provider.id),
        ])
        return

    venue_provider_job.delay(new_venue_provider.id)
Beispiel #22
0
def request_password_reset(body: RequestPasswordResetRequest) -> None:
    if feature_queries.is_active(FeatureToggle.ENABLE_NATIVE_APP_RECAPTCHA):
        try:
            api_recaptcha.check_native_app_recaptcha_token(body.token)
        except api_recaptcha.ReCaptchaException:
            raise ApiErrors({"token": "The given token is not invalid"})
    user = find_user_by_email(body.email)
    try:
        users_api.request_password_reset(user)
    except users_exceptions.EmailNotSent:
        raise ApiErrors(
            {"email": ["L'email n'a pas pu être envoyé"]},
            status_code=400,
        )
Beispiel #23
0
def _is_postal_code_eligible(code: str) -> bool:
    # FIXME (dbaty, 2020-01-14): remove this block once we have opened
    # to (almost) all departments.
    # Legacy behaviour: only a few departments are eligible.
    if not feature_queries.is_active(FeatureToggle.WHOLE_FRANCE_OPENING):
        for department in ELIGIBLE_DEPARTMENTS:
            if code.startswith(department):
                return True
        return False

    # New behaviour: all departments are eligible, except a few.
    for department in EXCLUDED_DEPARTMENTS:
        if code.startswith(department):
            return False
    return True
Beispiel #24
0
def generate_and_send_payments(payment_message_id: str = None):
    logger.info(
        "[BATCH][PAYMENTS] STEP 0 : validate bookings associated to outdated stocks"
    )
    if feature_queries.is_active(FeatureToggle.UPDATE_BOOKING_USED):
        update_booking_used_after_stock_occurrence()

    not_processable_payments, payments_to_send = generate_or_collect_payments(
        payment_message_id)

    try:
        logger.info("[BATCH][PAYMENTS] STEP 3 : send transactions")
        send_transactions(
            payments_to_send,
            settings.PASS_CULTURE_IBAN,
            settings.PASS_CULTURE_BIC,
            settings.PASS_CULTURE_REMITTANCE_CODE,
            settings.TRANSACTIONS_RECIPIENTS,
        )
    except Exception as e:  # pylint: disable=broad-except
        logger.exception("[BATCH][PAYMENTS] STEP 3: %s", e)

    try:
        logger.info("[BATCH][PAYMENTS] STEP 4 : send payments report")
        send_payments_report(payments_to_send + not_processable_payments,
                             settings.PAYMENTS_REPORT_RECIPIENTS)
    except Exception as e:  # pylint: disable=broad-except
        logger.exception("[BATCH][PAYMENTS] STEP 4: %s", e)

    try:
        logger.info("[BATCH][PAYMENTS] STEP 5 : send payments details")
        send_payments_details(payments_to_send,
                              settings.PAYMENTS_DETAILS_RECIPIENTS)
    except Exception as e:  # pylint: disable=broad-except
        logger.exception("[BATCH][PAYMENTS] STEP 5: %s", e)

    try:
        logger.info("[BATCH][PAYMENTS] STEP 6 : send wallet balances")
        send_wallet_balances(settings.WALLET_BALANCES_RECIPIENTS)
    except Exception as e:  # pylint: disable=broad-except
        logger.exception("[BATCH][PAYMENTS] STEP 6: %s", e)

    logger.info("[BATCH][PAYMENTS] generate_and_send_payments is done")
Beispiel #25
0
def create_account(body: serializers.AccountRequest) -> None:
    if feature_queries.is_active(FeatureToggle.ENABLE_NATIVE_APP_RECAPTCHA):
        try:
            api_recaptcha.check_native_app_recaptcha_token(body.token)
        except api_recaptcha.ReCaptchaException:
            raise ApiErrors({"token": "The given token is not invalid"})
    try:
        api.create_account(
            email=body.email,
            password=body.password,
            birthdate=body.birthdate,
            has_allowed_recommendations=body.has_allowed_recommendations,
            is_email_validated=False,
        )
    except exceptions.UserAlreadyExistsException:
        user = find_user_by_email(body.email)
        api.request_password_reset(user)
    except exceptions.UnderAgeUserException:
        raise ApiErrors({"dateOfBirth": "The birthdate is invalid"})
Beispiel #26
0
def edit_venue(venue_id):
    venue = load_or_404(Venue, venue_id)
    previous_venue = copy.deepcopy(venue)
    check_valid_edition(request, venue)
    validate_coordinates(request.json.get("latitude", None),
                         request.json.get("longitude", None))
    check_user_has_access_to_offerer(current_user, venue.managingOffererId)
    venue.populate_from_dict(request.json)

    if not venue.isVirtual:
        delete_venue_from_iris_venues(venue.id)

    repository.save(venue)
    link_valid_venue_to_irises(venue)

    if is_algolia_indexing(previous_venue, request.json):
        if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
            redis.add_venue_id(client=app.redis_client,
                               venue_id=dehumanize(venue_id))

    return jsonify(as_dict(venue, includes=VENUE_INCLUDES)), 200
Beispiel #27
0
def generate_new_payments() -> Tuple[List[Payment], List[Payment]]:
    offerers = Offerer.query.all()
    all_payments = []

    for offerer in offerers:
        if is_active(FeatureToggle.DEGRESSIVE_REIMBURSEMENT_RATE):
            booking_reimbursements = []
            for venue in offerer.managedVenues:
                final_bookings = booking_repository.find_bookings_eligible_for_payment_for_venue(
                    venue.id)
                booking_reimbursements += find_all_booking_reimbursements(
                    final_bookings, NEW_RULES)
        else:
            final_bookings = booking_repository.find_bookings_eligible_for_payment_for_offerer(
                offerer.id)
            booking_reimbursements = find_all_booking_reimbursements(
                final_bookings, CURRENT_RULES)

        booking_reimbursements_to_pay = filter_out_already_paid_for_bookings(
            filter_out_bookings_without_cost(booking_reimbursements))

        with db.session.no_autoflush:
            payments = list(
                map(create_payment_for_booking, booking_reimbursements_to_pay))

        if payments:
            repository.save(*payments)
            all_payments.extend(payments)
        logger.info("[BATCH][PAYMENTS] Saved %i payments for offerer : %s",
                    len(payments), offerer.name)

    logger.info(
        "[BATCH][PAYMENTS] Generated %i payments for %i offerers in total",
        len(all_payments), len(offerers))

    pending_payments = keep_only_pending_payments(all_payments)
    not_processable_payments = keep_only_not_processable_payments(all_payments)
    logger.info("[BATCH][PAYMENTS] %s Payments in status PENDING to send",
                len(pending_payments))
    return pending_payments, not_processable_payments
Beispiel #28
0
def validate_new_offerer(token):
    check_validation_request(token)
    offerer = Offerer.query.filter_by(validationToken=token).first()
    check_validation_token_has_been_already_used(offerer)
    offerer.validationToken = None
    managed_venues = offerer.managedVenues

    for venue in managed_venues:
        link_valid_venue_to_irises(venue)

    repository.save(offerer)
    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        venue_ids = map(lambda managed_venue: managed_venue.id, managed_venues)
        sorted_venue_ids = sorted(venue_ids, key=int)
        for venue_id in sorted_venue_ids:
            redis.add_venue_id(client=app.redis_client, venue_id=venue_id)

    try:
        send_validation_confirmation_email_to_pro(offerer, send_raw_email)
    except MailServiceException as mail_service_exception:
        app.logger.exception("Email service failure", mail_service_exception)
    return "Validation effectuée", 202
Beispiel #29
0
def cancel_booking_by_beneficiary(user: User, booking: Booking) -> None:
    if not user.isBeneficiary:
        raise RuntimeError(
            "Unexpected call to cancel_booking_by_beneficiary with non-beneficiary user %s"
            % user)
    validation.check_beneficiary_can_cancel_booking(user, booking)

    booking.isCancelled = True
    booking.cancellationReason = BookingCancellationReasons.BENEFICIARY
    repository.save(booking)

    try:
        user_emails.send_booking_cancellation_emails_to_user_and_offerer(
            booking, booking.cancellationReason)
    except MailServiceException as error:
        logger.exception(
            "Could not send booking=%s cancellation emails to user and offerer: %s",
            booking.id, error)

    if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
        redis.add_offer_id(client=app.redis_client,
                           offer_id=booking.stock.offerId)
Beispiel #30
0
    def updateObjects(self, limit=None):
        # pylint: disable=too-many-nested-blocks
        if self.venue_provider and not self.venue_provider.isActive:
            logger.info("Venue provider %s is inactive", self.venue_provider)
            return

        if not self.provider.isActive:
            provider_name = self.__class__.__name__
            logger.info("Provider %s is inactive", provider_name)
            return

        self.log_provider_event(LocalProviderEventType.SyncStart)

        chunk_to_insert = {}
        chunk_to_update = {}

        for providable_infos in self:
            objects_limit_reached = limit and self.checkedObjects >= limit
            if objects_limit_reached:
                break

            has_no_providables_info = len(providable_infos) == 0
            if has_no_providables_info:
                self.checkedObjects += 1
                continue

            for providable_info in providable_infos:
                chunk_key = providable_info.id_at_providers + "|" + str(providable_info.type.__name__)
                pc_object = get_existing_pc_obj(providable_info, chunk_to_insert, chunk_to_update)

                if pc_object is None:
                    if not self.can_create:
                        continue

                    try:
                        pc_object = self._create_object(providable_info)
                        chunk_to_insert[chunk_key] = pc_object
                    except ApiErrors:
                        continue
                else:
                    last_update_for_current_provider = get_last_update_for_provider(self.provider.id, pc_object)
                    object_need_update = (
                        last_update_for_current_provider is None
                        or last_update_for_current_provider < providable_info.date_modified_at_provider
                    )

                    if object_need_update:
                        try:
                            self._handle_update(pc_object, providable_info)
                            if chunk_key in chunk_to_insert:
                                chunk_to_insert[chunk_key] = pc_object
                            else:
                                chunk_to_update[chunk_key] = pc_object
                        except ApiErrors:
                            continue

                if isinstance(pc_object, HasThumbMixin):
                    initial_thumb_count = pc_object.thumbCount
                    try:
                        self._handle_thumb(pc_object)
                    except Exception as e:  # pylint: disable=broad-except
                        self.log_provider_event(LocalProviderEventType.SyncError, e.__class__.__name__)
                        self.erroredThumbs += 1
                        logger.info("ERROR during handle thumb: %s", e, exc_info=True)
                    pc_object_has_new_thumbs = pc_object.thumbCount != initial_thumb_count
                    if pc_object_has_new_thumbs:
                        errors = entity_validator.validate(pc_object)
                        if errors and len(errors.errors) > 0:
                            self.log_provider_event(LocalProviderEventType.SyncError, "ApiErrors")
                            continue

                        chunk_to_update[chunk_key] = pc_object

                self.checkedObjects += 1

                if len(chunk_to_insert) + len(chunk_to_update) >= CHUNK_MAX_SIZE:
                    save_chunks(chunk_to_insert, chunk_to_update)
                    chunk_to_insert = {}
                    chunk_to_update = {}

        if len(chunk_to_insert) + len(chunk_to_update) > 0:
            save_chunks(chunk_to_insert, chunk_to_update)

        self._print_objects_summary()
        self.log_provider_event(LocalProviderEventType.SyncEnd)

        if self.venue_provider is not None:
            self.venue_provider.lastSyncDate = datetime.utcnow()
            self.venue_provider.syncWorkerId = None
            repository.save(self.venue_provider)
            if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA):
                send_venue_provider_data_to_redis(self.venue_provider)