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-29597)[API] chore : read duration minutes from produts #14596

Open
wants to merge 4 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
6 changes: 3 additions & 3 deletions api/src/pcapi/core/offers/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def _create(
kwargs["subcategoryId"] = product.subcategoryId
kwargs["description"] = None
kwargs["extraData"] = product.extraData
kwargs["durationMinutes"] = product.durationMinutes
kwargs["durationMinutes"] = None
else:
if "extraData" not in kwargs:
subcategory_id = kwargs.get("subcategoryId")
Expand All @@ -224,8 +224,8 @@ def _check_offer_kwargs(product: models.Product, kwargs: dict[str, typing.Any])
raise ValueError("SubcategoryId of the offer and the product must be the same")
if kwargs.get("extraData") and kwargs.get("extraData") != product.extraData:
raise ValueError("ExtraData of the offer and the product must be the same")
if kwargs.get("durationMinutes") and kwargs.get("durationMinutes") != product.durationMinutes:
raise ValueError("DurationMinutes of the offer and the product must be the same")
if kwargs.get("durationMinutes"):
raise ValueError("DurationMinutes of the offer must be None when product is given")
if kwargs.get("description"):
raise ValueError("Description of the offer must be None when product is given")

Expand Down
15 changes: 14 additions & 1 deletion api/src/pcapi/core/offers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ def __table_args__(self):
sa.DateTime, nullable=True, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow
)
_description = sa.Column("description", sa.Text, nullable=True)
durationMinutes = sa.Column(sa.Integer, nullable=True)
_durationMinutes = sa.Column("durationMinutes", sa.Integer, nullable=True)
externalTicketOfficeUrl = sa.Column(sa.String, nullable=True)
extraData: OfferExtraData | None = sa.Column("jsonData", sa_mutable.MutableDict.as_mutable(postgresql.JSONB))
fieldsUpdated: list[str] = sa.Column(
Expand Down Expand Up @@ -622,6 +622,19 @@ def description(self, value: str | None) -> None:
else:
self._description = value

@property
def durationMinutes(self) -> int | None:
if self.product:
return self.product.durationMinutes
return self._durationMinutes

@durationMinutes.setter
def durationMinutes(self, value: int | None) -> None:
if self.product:
self._durationMinutes = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

il n'y a pas de risque que le pro remplisse le champ et ne voit pas la modification attendue ?
Est-ce que l'on ne devrait pas lever une erreur explicite ?

Copy link
Contributor

@tconte-pass tconte-pass Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On veut pas un warning ou autre pour dire que la fonction n'a pas l'effet escompté ? Voire une exception

else:
self._durationMinutes = value

@property
def isEducational(self) -> bool:
return False
Expand Down
1 change: 1 addition & 0 deletions api/src/pcapi/core/offers/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def get_offers_details(offer_ids: list[int]) -> BaseQuery:
models.Product.description,
models.Product.last_30_days_booking,
models.Product.thumbCount,
models.Product.durationMinutes,
)
.joinedload(models.Product.productMediations)
)
Expand Down
1 change: 0 additions & 1 deletion api/src/pcapi/local_providers/allocine/allocine_stocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ def fill_object_attributes(self, pc_object: Model) -> None:
def update_from_movie_information(self, offer: offers_models.Offer) -> None:
offer.name = self.product.name
offer.extraData = offer.extraData or offers_models.OfferExtraData()
offer.durationMinutes = self.product.durationMinutes
offer.product = self.product
if self.product.extraData:
offer.extraData.update(self.product.extraData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ def update_from_movie_information(
offer.extraData = offer.extraData or offers_models.OfferExtraData()
if self.product:
offer.name = self.product.name
offer.durationMinutes = self.product.durationMinutes
if self.product.extraData:
offer.extraData.update(self.product.extraData)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ def update_from_movie_information(self, offer: offers_models.Offer) -> None:
offer.extraData = offer.extraData or offers_models.OfferExtraData()
if self.product:
offer.name = self.product.name
offer.durationMinutes = self.product.durationMinutes
if self.product.extraData:
offer.extraData.update(self.product.extraData)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ def update_from_movie_information(self, offer: offers_models.Offer) -> None:
offer.extraData = offer.extraData or offers_models.OfferExtraData()
if self.product:
offer.name = self.product.name
offer.description = self.product.description
offer.durationMinutes = self.product.durationMinutes
if self.product.extraData:
offer.extraData.update(self.product.extraData)
else:
Expand Down
33 changes: 16 additions & 17 deletions api/src/pcapi/local_providers/cinema_providers/ems/ems_stocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ def synchronize(self) -> None:

product = self.get_or_create_movie_product(event)
offer = self.get_or_create_offer(event, self.provider.id, self.venue)
offer.product = product
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On veut garder cette ligne non ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je l'ai décalée plus bas dans le fill_offer_attribute. Si je la laisse ici j'ai un auto_flush si je fait un if offer.product: sqlalchemy flush l'offre et j'ai une erreur.
Dans le if product: j'ai mis le offer.product = product

offer = self.fill_offer_attributes(offer, event)
offer = self.fill_offer_attributes(offer, product, event)
errors = entity_validator.validate(offer)
if errors and len(errors.errors) > 0:
self.created_objects -= 1
Expand Down Expand Up @@ -156,23 +155,23 @@ def get_or_create_offer(
self.created_objects += 1
return offer

def fill_offer_attributes(self, offer: offers_models.Offer, event: ems_serializers.Event) -> offers_models.Offer:
if event.title:
offer.name = event.title
if event.synopsis:
offer.description = event.synopsis
if event.duration:
offer.durationMinutes = event.duration
def fill_offer_attributes(
self, offer: offers_models.Offer, product: offers_models.Product | None, event: ems_serializers.Event
) -> offers_models.Offer:
offer.isDuo = self.is_duo

if offer.product:
offer.name = offer.product.name
offer.description = offer.product.description
offer.durationMinutes = offer.product.durationMinutes
if offer.product.extraData:
if product:
offer.name = product.name
if product.extraData:
offer.extraData = offer.extraData or offers_models.OfferExtraData()
offer.extraData.update(offer.product.extraData)

offer.extraData.update(product.extraData)
offer.product = product
else:
if event.title:
offer.name = event.title
if event.synopsis:
offer.description = event.synopsis
if event.duration:
offer.durationMinutes = event.duration
return offer

def get_or_create_stock(
Expand Down
7 changes: 6 additions & 1 deletion api/src/pcapi/routes/public/individual_offers/v1/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ def retrieve_offer_relations_query(query: sqla_orm.Query) -> sqla_orm.Query:
.options(sqla_orm.joinedload(offers_models.Offer.mediations))
.options(
sqla_orm.joinedload(offers_models.Offer.product)
.load_only(offers_models.Product.id, offers_models.Product.thumbCount, offers_models.Product.description)
.load_only(
offers_models.Product.id,
offers_models.Product.thumbCount,
offers_models.Product.description,
offers_models.Product.durationMinutes,
)
.joinedload(offers_models.Product.productMediations)
)
.options(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ def _create_offer_and_stocks_for_allocine_venues(
for venue in venues:
for idx, product in enumerate(products):
movie_offer = offers_factories.OfferFactory(
durationMinutes=product.durationMinutes,
name=product.name,
product=product,
subcategoryId=subcategories_v2.SEANCE_CINE.id,
Expand Down
14 changes: 8 additions & 6 deletions api/src/pcapi/validation/models/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def validate_generic(model: Model) -> ApiErrors:

column = columns[key]
value = getattr(model, key)

column_name = column.expression.name
if ( # pylint: disable=too-many-boolean-expressions
not getattr(column.expression, "nullable", False)
and not column.expression.foreign_keys
Expand All @@ -37,7 +37,7 @@ def validate_generic(model: Model) -> ApiErrors:
and getattr(column.expression, "server_default", ...) is None
and value is None
):
api_errors.add_error(key, "Cette information est obligatoire")
api_errors.add_error(column_name, "Cette information est obligatoire")

if value is None:
continue
Expand All @@ -47,20 +47,22 @@ def validate_generic(model: Model) -> ApiErrors:
and not isinstance(column.expression.type, sqlalchemy.Enum)
and not isinstance(value, str)
):
api_errors.add_error(key, "doit être une chaîne de caractères")
api_errors.add_error(column_name, "doit être une chaîne de caractères")

if (
isinstance(column.expression.type, (String, CHAR))
and isinstance(value, str)
and column.expression.type.length
and len(value) > column.expression.type.length
):
api_errors.add_error(key, f"Vous devez saisir moins de {str(column.expression.type.length)} caractères")
api_errors.add_error(
column_name, f"Vous devez saisir moins de {str(column.expression.type.length)} caractères"
)

if isinstance(column.expression.type, Integer) and not isinstance(value, int):
api_errors.add_error(key, "doit être un entier")
api_errors.add_error(column_name, "doit être un entier")

if isinstance(column.expression.type, Float) and not isinstance(value, float):
api_errors.add_error(key, "doit être un nombre")
api_errors.add_error(column_name, "doit être un nombre")

return api_errors
21 changes: 12 additions & 9 deletions api/tests/routes/backoffice/offers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1631,19 +1631,11 @@ def test_get_detail_offer_with_product(self, authenticated_client):
assert response.status_code == 200

def test_get_detail_event_offer(self, authenticated_client):
offer = offers_factories.OfferFactory(
product = offers_factories.ProductFactory(
name="good movie",
offererAddress=None,
subcategoryId=subcategories.SEANCE_CINE.id,
durationMinutes=133,
description="description",
idAtProvider="pouet provider",
isActive=True,
isDuo=False,
audioDisabilityCompliant=True,
mentalDisabilityCompliant=False,
motorDisabilityCompliant=None,
visualDisabilityCompliant=False,
extraData={
"cast": [
"first actor",
Expand Down Expand Up @@ -1698,6 +1690,17 @@ def test_get_detail_event_offer(self, authenticated_client):
"performer": "John Doe",
},
)
offer = offers_factories.OfferFactory(
product=product,
offererAddress=None,
idAtProvider="pouet provider",
isActive=True,
isDuo=False,
audioDisabilityCompliant=True,
mentalDisabilityCompliant=False,
motorDisabilityCompliant=None,
visualDisabilityCompliant=False,
)

url = url_for(self.endpoint, offer_id=offer.id, _external=True)

Expand Down
17 changes: 16 additions & 1 deletion api/tests/routes/pro/get_offer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def test_returns_a_thing_stock(self, client):
offer = offers_factories.OfferFactory(
product=product, venue__managingOfferer=user_offerer.offerer, subcategoryId=subcategories.LIVRE_PAPIER.id
)
offers_factories.ThingStockFactory()
offers_factories.ThingStockFactory(offer=offer)
offer_id = offer.id

client = client.with_session_auth(email=user_offerer.user.email)
Expand All @@ -247,6 +247,21 @@ def test_returns_a_thing_stock(self, client):
data = response.json
assert data["subcategoryId"] == "LIVRE_PAPIER"

def test_returns_a_movie_with_product(self, client):
user_offerer = offerers_factories.UserOffererFactory()
product = offers_factories.ProductFactory(subcategoryId=subcategories.SEANCE_CINE.id, durationMinutes=120)
offer = offers_factories.OfferFactory(product=product, venue__managingOfferer=user_offerer.offerer)
offers_factories.EventStockFactory(offer=offer)
offer_id = offer.id

client = client.with_session_auth(email=user_offerer.user.email)
with testing.assert_num_queries(self.num_queries):
response = client.get(f"/offers/{offer_id}")
assert response.status_code == 200

data = response.json
assert data["subcategoryId"] == subcategories.SEANCE_CINE.id

@time_machine.travel("2019-10-15 00:00:00")
def test_returns_a_thing_with_activation_code_stock(self, client):
user_offerer = offerers_factories.UserOffererFactory()
Expand Down
Loading