Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PC-33865)[API] chore: add atomic to offer routes part 2 #16242

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from functools import partial

import sqlalchemy as sqla

from pcapi.core import mails
Expand All @@ -10,6 +12,7 @@
from pcapi.core.offers.models import Offer
from pcapi.core.offers.models import Stock
from pcapi.core.users.models import User
from pcapi.repository import on_commit
from pcapi.tasks.mails_tasks import send_withdrawal_detail_changed_emails
from pcapi.tasks.serialization.mails_tasks import WithdrawalChangedMailBookingDetail
from pcapi.tasks.serialization.mails_tasks import WithdrawalChangedMailRequest
Expand Down Expand Up @@ -67,7 +70,12 @@ def send_email_for_each_ongoing_booking(offer: Offer) -> None:
offer_address=booking.stock.offer.fullAddress,
)
)
send_withdrawal_detail_changed_emails.delay(mails_request)
on_commit(
partial(
send_withdrawal_detail_changed_emails.delay,
payload=mails_request,
)
)


def send_booking_withdrawal_updated(
Expand Down
53 changes: 32 additions & 21 deletions api/src/pcapi/core/offers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,14 @@ def update_offer(
if should_send_mail and (withdrawal_updated or oa_updated):
transactional_mails.send_email_for_each_ongoing_booking(offer)

reason = search.IndexationReason.OFFER_UPDATE
search.async_index_offer_ids([offer.id], reason=reason, log_extra={"changes": updates_set})
on_commit(
partial(
search.async_index_offer_ids,
[offer.id],
reason=search.IndexationReason.OFFER_UPDATE,
log_extra={"changes": updates_set},
)
)

return offer

Expand Down Expand Up @@ -627,12 +633,7 @@ def batch_update_offers(query: BaseQuery, update_fields: dict, send_email_notifi
)
if send_email_notification and withdrawal_updated:
for offer in query_to_update.all():
on_commit(
partial(
transactional_mails.send_email_for_each_ongoing_booking,
offer,
),
)
transactional_mails.send_email_for_each_ongoing_booking(offer)


def archive_collective_offers(
Expand Down Expand Up @@ -1029,11 +1030,14 @@ def edit_stock(
if "beginningDatetime" in modifications:
finance_api.update_finance_event_pricing_date(stock)

repository.add_to_session(stock)
search.async_index_offer_ids(
[stock.offerId],
reason=search.IndexationReason.STOCK_UPDATE,
log_extra={"changes": set(modifications.keys())},
db.session.add(stock)
on_commit(
partial(
search.async_index_offer_ids,
[stock.offerId],
reason=search.IndexationReason.STOCK_UPDATE,
log_extra={"changes": set(modifications.keys())},
),
)

log_extra_data: dict[str, typing.Any] = {
Expand Down Expand Up @@ -1094,9 +1098,13 @@ def publish_offer(
else:
if offer.publicationDate:
offers_repository.delete_future_offer(offer.id)
search.async_index_offer_ids(
[offer.id],
reason=search.IndexationReason.OFFER_PUBLICATION,

on_commit(
partial(
search.async_index_offer_ids,
[offer.id],
reason=search.IndexationReason.OFFER_PUBLICATION,
)
)
logger.info(
"Offer has been published",
Expand Down Expand Up @@ -1240,9 +1248,12 @@ def delete_mediation(offer: models.Offer) -> None:

_delete_mediations_and_thumbs(mediations)

search.async_index_offer_ids(
[offer.id],
reason=search.IndexationReason.MEDIATION_DELETION,
on_commit(
partial(
search.async_index_offer_ids,
[offer.id],
reason=search.IndexationReason.MEDIATION_DELETION,
),
)


Expand Down Expand Up @@ -1810,7 +1821,7 @@ def create_price_category(
created_price_category = models.PriceCategory(
offer=offer, price=price, priceCategoryLabel=price_category_label, idAtProvider=id_at_provider
)
repository.add_to_session(created_price_category)
db.session.add(created_price_category)
return created_price_category


Expand Down Expand Up @@ -1840,7 +1851,7 @@ def edit_price_category(
)
price_category.idAtProvider = id_at_provider

repository.add_to_session(price_category)
db.session.add(price_category)

stocks_to_edit = [stock for stock in offer.stocks if stock.priceCategoryId == price_category.id]
for stock in stocks_to_edit:
Expand Down
103 changes: 49 additions & 54 deletions api/src/pcapi/routes/pro/offers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from sqlalchemy.orm import load_only
import sqlalchemy.orm.exc as sa_exceptions

from pcapi import repository
from pcapi.core.categories import categories
from pcapi.core.categories import subcategories_v2 as subcategories
from pcapi.core.categories.categories import TITELIVE_MUSIC_TYPES
Expand All @@ -25,9 +24,7 @@
from pcapi.core.offers.validation import check_for_duplicated_price_categories
from pcapi.core.offers.validation import check_product_cgu_and_offerer
from pcapi.models import api_errors
from pcapi.models import db
from pcapi.repository import atomic
from pcapi.repository import transaction
from pcapi.routes.apis import private_api
from pcapi.routes.serialization import offers_serialize
from pcapi.routes.serialization.thumbnails_serialize import CreateThumbnailBodyModel
Expand Down Expand Up @@ -350,34 +347,31 @@ def post_offer(body: offers_serialize.PostOfferBodyModel) -> offers_serialize.Ge
api=blueprint.pro_private_schema,
response_model=offers_serialize.GetIndividualOfferResponseModel,
)
@atomic()
def patch_publish_offer(
body: offers_serialize.PatchOfferPublishBodyModel,
) -> offers_serialize.GetIndividualOfferResponseModel:
with repository.transaction():
with db.session.no_autoflush:
try:
offerer = offerers_repository.get_by_offer_id(body.id)
except offerers_exceptions.CannotFindOffererForOfferId:
raise api_errors.ApiErrors(
{"offerer": ["Aucune structure trouvée à partir de cette offre"]}, status_code=404
)
try:
offerer = offerers_repository.get_by_offer_id(body.id)
except offerers_exceptions.CannotFindOffererForOfferId:
raise api_errors.ApiErrors({"offerer": ["Aucune structure trouvée à partir de cette offre"]}, status_code=404)

rest.check_user_has_access_to_offerer(current_user, offerer.id)
rest.check_user_has_access_to_offerer(current_user, offerer.id)

offer = offers_repository.get_offer_and_extradata(body.id)
if offer is None:
raise api_errors.ApiErrors({"offer": ["Cette offre n’existe pas"]}, status_code=404)
if not offers_repository.offer_has_bookable_stocks(offer.id):
raise api_errors.ApiErrors({"offer": "Cette offre n’a pas de stock réservable"}, 400)
offer = offers_repository.get_offer_and_extradata(body.id)
if offer is None:
raise api_errors.ApiErrors({"offer": ["Cette offre n’existe pas"]}, status_code=404)
if not offers_repository.offer_has_bookable_stocks(offer.id):
raise api_errors.ApiErrors({"offer": "Cette offre n’a pas de stock réservable"}, 400)

try:
offers_api.publish_offer(offer, current_user, publication_date=body.publicationDate)
except exceptions.FutureOfferException as exc:
raise api_errors.ApiErrors(exc.errors, status_code=400)
except (exceptions.OfferCreationBaseException, exceptions.OfferEditionBaseException) as exc:
raise api_errors.ApiErrors(exc.errors, status_code=400)
try:
offers_api.publish_offer(offer, current_user, publication_date=body.publicationDate)
except exceptions.FutureOfferException as exc:
raise api_errors.ApiErrors(exc.errors, status_code=400)
except (exceptions.OfferCreationBaseException, exceptions.OfferEditionBaseException) as exc:
raise api_errors.ApiErrors(exc.errors, status_code=400)

return offers_serialize.GetIndividualOfferResponseModel.from_orm(offer)
return offers_serialize.GetIndividualOfferResponseModel.from_orm(offer)


@private_api.route("/offers/active-status", methods=["PATCH"])
Expand All @@ -387,6 +381,7 @@ def patch_publish_offer(
on_success_status=204,
api=blueprint.pro_private_schema,
)
@atomic()
def patch_offers_active_status(body: offers_serialize.PatchOfferActiveStatusBodyModel) -> None:
query = offers_repository.get_offers_by_ids(current_user, body.ids)
if body.is_active:
Expand Down Expand Up @@ -427,6 +422,7 @@ def patch_all_offers_active_status(
response_model=offers_serialize.GetIndividualOfferResponseModel,
api=blueprint.pro_private_schema,
)
@atomic()
def patch_offer(
offer_id: int, body: offers_serialize.PatchOfferBodyModel
) -> offers_serialize.GetIndividualOfferResponseModel:
Expand All @@ -445,14 +441,13 @@ def patch_offer(

rest.check_user_has_access_to_offerer(current_user, offer.venue.managingOffererId)
try:
with repository.transaction():
updates = body.dict(by_alias=True, exclude_unset=True)
if body_extra_data := offers_api.deserialize_extra_data(body.extraData, offer.subcategoryId):
updates["extraData"] = body_extra_data
updates = body.dict(by_alias=True, exclude_unset=True)
if body_extra_data := offers_api.deserialize_extra_data(body.extraData, offer.subcategoryId):
updates["extraData"] = body_extra_data

offer_body = offers_schemas.UpdateOffer(**updates)
offer_body = offers_schemas.UpdateOffer(**updates)

offer = offers_api.update_offer(offer, offer_body, is_from_private_api=True)
offer = offers_api.update_offer(offer, offer_body, is_from_private_api=True)
except (exceptions.OfferCreationBaseException, exceptions.OfferEditionBaseException) as error:
raise api_errors.ApiErrors(error.errors, status_code=400)

Expand Down Expand Up @@ -498,13 +493,13 @@ def create_thumbnail(form: CreateThumbnailBodyModel) -> CreateThumbnailResponseM
on_success_status=204,
api=blueprint.pro_private_schema,
)
@atomic()
def delete_thumbnail(offer_id: int) -> None:
offer = models.Offer.query.get_or_404(offer_id)

rest.check_user_has_access_to_offerer(current_user, offer.venue.managingOffererId)

with transaction():
offers_api.delete_mediation(offer=offer)
offers_api.delete_mediation(offer=offer)


@private_api.route("/offers/categories", methods=["GET"])
Expand Down Expand Up @@ -588,6 +583,7 @@ def _get_offer_for_price_categories_upsert(
response_model=offers_serialize.GetIndividualOfferResponseModel,
api=blueprint.pro_private_schema,
)
@atomic()
def post_price_categories(
offer_id: int, body: offers_serialize.PriceCategoryBody
) -> offers_serialize.GetIndividualOfferResponseModel:
Expand All @@ -612,28 +608,27 @@ def post_price_categories(

existing_price_categories_by_id = {category.id: category for category in offer.priceCategories}

with repository.transaction():
for price_category_to_create in price_categories_to_create:
offers_api.create_price_category(offer, price_category_to_create.label, price_category_to_create.price)

for price_category_to_edit in price_categories_to_edit:
if price_category_to_edit.id not in existing_price_categories_by_id:
raise api_errors.ApiErrors(
{"price_category_id": ["Le tarif avec l'id %s n'existe pas" % price_category_to_edit.id]},
status_code=400,
)
data = price_category_to_edit.dict(exclude_unset=True)
try:
offers_api.edit_price_category(
offer,
price_category=existing_price_categories_by_id[data["id"]],
label=data.get("label", offers_api.UNCHANGED),
price=data.get("price", offers_api.UNCHANGED),
)
except exceptions.RejectedOrPendingOfferNotEditable:
raise api_errors.ApiErrors(
{"offer": ["Offer is not editable (because rejected or pending)"]}, status_code=400
)
for price_category_to_create in price_categories_to_create:
offers_api.create_price_category(offer, price_category_to_create.label, price_category_to_create.price)

for price_category_to_edit in price_categories_to_edit:
if price_category_to_edit.id not in existing_price_categories_by_id:
raise api_errors.ApiErrors(
{"price_category_id": ["Le tarif avec l'id %s n'existe pas" % price_category_to_edit.id]},
status_code=400,
)
data = price_category_to_edit.dict(exclude_unset=True)
try:
offers_api.edit_price_category(
offer,
price_category=existing_price_categories_by_id[data["id"]],
label=data.get("label", offers_api.UNCHANGED),
price=data.get("price", offers_api.UNCHANGED),
)
except exceptions.RejectedOrPendingOfferNotEditable:
raise api_errors.ApiErrors(
{"offer": ["Offer is not editable (because rejected or pending)"]}, status_code=400
)

return offers_serialize.GetIndividualOfferResponseModel.from_orm(offer)

Expand Down
Loading