From 4240015d517f0994a949e7d63a2be59ebceaf1d4 Mon Sep 17 00:00:00 2001 From: Valentin Ambroise <113367796+vaamb@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:37:09 +0100 Subject: [PATCH] Partially revert #172 to fix foreign key constrains in `WikiPicture` (#177) --- src/ouranos/core/database/models/app.py | 76 +++++++++++++++++++++---- tests/web_server/routes/wiki.py | 2 - 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/ouranos/core/database/models/app.py b/src/ouranos/core/database/models/app.py index b9f0af1..dd457d7 100644 --- a/src/ouranos/core/database/models/app.py +++ b/src/ouranos/core/database/models/app.py @@ -1326,21 +1326,20 @@ class WikiPicture(Base, WikiTagged, CRUDMixin, WikiObject): __bind_key__ = "app" __table_args__ = ( UniqueConstraint( - "topic_name", "article_name", "name", + "article_id", "name", name="uq_wiki_pictures_name" ), ) - _lookup_keys = ["topic_name", "article_name", "name"] + _lookup_keys = ["article_id", "name"] # Search by topic_name, article_name and name id: Mapped[int] = mapped_column(primary_key=True) - topic_name: Mapped[str] = mapped_column(sa.ForeignKey("wiki_topics.name")) - article_name: Mapped[str] = mapped_column(sa.ForeignKey("wiki_articles.name")) + article_id: Mapped[int] = mapped_column(sa.ForeignKey("wiki_articles.id")) name: Mapped[str] = mapped_column(sa.String(length=64)) path: Mapped[ioPath] = mapped_column(PathType(length=512)) status: Mapped[bool] = mapped_column(default=True) # relationship - article: Mapped[WikiArticle] = relationship(back_populates="pictures") + article: Mapped[WikiArticle] = relationship(back_populates="pictures", lazy="selectin") def __repr__(self) -> str: return ( @@ -1348,6 +1347,14 @@ def __repr__(self) -> str: f"path={self.path})>" ) + @property + def topic_name(self) -> str: + return self.article.topic_name + + @property + def article_name(self) -> str: + return self.article.name + @property def absolute_path(self) -> ioPath: return self.root_dir() / self.path @@ -1361,6 +1368,30 @@ async def get_image(self) -> bytes: image = await f.read() return image + @classmethod + def _generate_get_query( + cls, + offset: int | None = None, + limit: int | None = None, + order_by: str | None = None, + **lookup_keys: list[lookup_keys_type] | lookup_keys_type | None, + ) -> Select: + lookup_keys["status"] = True + topic_name = lookup_keys.pop("topic_name", None) + article_name = lookup_keys.pop("article_name", None) + stmt = super()._generate_get_query(offset, limit, order_by, **lookup_keys) + if topic_name or article_name: + stmt = stmt.join(WikiArticle.pictures) + if isinstance(topic_name, list): + stmt = stmt.where(WikiArticle.topic_name.in_(topic_name)) + else: + stmt = stmt.where(WikiArticle.topic_name == topic_name) + if isinstance(article_name, list): + stmt = stmt.where(WikiArticle.name.in_(article_name)) + else: + stmt = stmt.where(WikiArticle.name == article_name) + return stmt + @classmethod async def create( cls, @@ -1369,12 +1400,13 @@ async def create( values: dict | None = None, # author_id, content, extension **lookup_keys: lookup_keys_type, # article_id, name ) -> None: - topic_name = lookup_keys["topic_name"] - article_name = lookup_keys["article_name"] + topic_name = lookup_keys.pop("topic_name") + article_name = lookup_keys.pop("article_name") article = await WikiArticle.get( session, topic_name=topic_name, name=article_name) if article is None: raise WikiArticleNotFound + lookup_keys["article_id"] = article.id # Get extension info name: str = lookup_keys["name"] extension: str = values.pop("extension") @@ -1397,6 +1429,23 @@ async def create( session, topic_name=topic_name, article_name=article_name, name=name) await picture.set_image(content) + @classmethod + async def update( + cls, + session: AsyncSession, + /, + values: dict | None = None, # author_id, content, extension + **lookup_keys: lookup_keys_type, # article_id, name + ) -> None: + topic_name = lookup_keys.pop("topic_name") + article_name = lookup_keys.pop("article_name") + article = await WikiArticle.get( + session, topic_name=topic_name, name=article_name) + if article is None: + raise WikiArticleNotFound + lookup_keys["article_id"] = article.id + await super().update(session, values=values, **lookup_keys) + @classmethod async def delete( cls, @@ -1405,15 +1454,18 @@ async def delete( values: dict | None = None, # author_id, content **lookup_keys: lookup_keys_type, # article_id, name ) -> None: - topic_name = lookup_keys["topic_name"] - article_name = lookup_keys["article_name"] + topic_name = lookup_keys.pop("topic_name") + article_name = lookup_keys.pop("article_name") + article = await WikiArticle.get( + session, topic_name=topic_name, name=article_name) + if article is None: + raise WikiArticleNotFound name: str = lookup_keys["name"] - picture = await cls.get( - session, topic_name=topic_name, article_name=article_name, name=name) + picture = await cls.get(session, article_id=article.id, name=name) if picture is None: raise WikiArticleNotFound # Mark the picture as inactive - await super().update( + await cls.update( session, topic_name=topic_name, article_name=article_name, name=name, values={"status": False}) # Rename the picture content diff --git a/tests/web_server/routes/wiki.py b/tests/web_server/routes/wiki.py index 903f52b..3daab50 100644 --- a/tests/web_server/routes/wiki.py +++ b/tests/web_server/routes/wiki.py @@ -305,13 +305,11 @@ async def test_create_picture( "content": content.decode("utf-8"), } # Run test - x = 1 response = client_operator.post( f"/api/app/services/wiki/topics/u/{wiki_topic_name}/" f"u/{wiki_article_name}/u", json=payload, ) - x = 1 assert response.status_code == 200 async with db.scoped_session() as session: