pdf-quiz-generator/backend/alembic/versions/c5d6e7f8091a_collectible_articles.py
Daniel 52ef7acdea feat: articles are collectible, and Summary is a toggle
A library holds both articles and questions now. It held only questions, so the
bookmark on an article had nowhere to write and stood in for the questions
filed under the topic instead — which is not what a reader who saved the
reading asked for, and left a topic with no questions unsaveable. Its own
table rather than a nullable column beside `question_id`: that shape allows a
row with both or neither, and every read then has to say which kind it is
looking at.

Which libraries already hold an article is now asked of the server, as one
question. It was kept on the device because the API could not answer, which was
wrong on the second machine and silently so. Putting one back is the same
control rather than an undo somewhere else.

"Short" is called Summary, because that is what the section is called, and it
is a toggle rather than one tab of three — the whole topic, or the part of it
worth revising, which is a different kind of choice from Long versus Clinical.
It names its own state, so a reader can tell why two thirds of the contents are
not there. The stored variant stays `short`: renaming it would be a data
migration to change a word on a button.

Also: `litellm==1.28.13` has been withdrawn from PyPI, so requirements.txt
could not be edited at all without the pip layer failing to rebuild — which is
what blocked pinning Pillow. Repinned to 1.53.1, the nearest still published;
the three things we use are unchanged in it, and both suites pass on the new
set. Pillow is pinned properly now rather than arriving through PyMuPDF.

One consequence, handled: `litellm.utils.get_valid_models()` now returns
nothing unless a provider's own API key is in the environment, and ours is a
proxy. That branch is only reached when no proxy is configured, and it now says
so instead of answering with an empty list that reads as "this site has no
models".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-12 14:52:20 +02:00

44 lines
1.8 KiB
Python

"""A library can hold articles as well as questions.
Collections were questions only, so the reading side of the site had no way to
put anything aside — the bookmark on an article had nowhere to write. Its own
table rather than a nullable column beside `question_id`, so a row cannot claim
to be both kinds or neither.
Revision ID: c5d6e7f8091a
Revises: b4c5d6e7f809
"""
import sqlalchemy as sa
from alembic import op
revision = "c5d6e7f8091a"
down_revision = "b4c5d6e7f809"
branch_labels = None
depends_on = None
def upgrade():
# `Base.metadata.create_all()` still runs at startup and creates missing
# tables, so on a box that has already booted this code the table is here
# before the migration is. Checked rather than assumed, or the upgrade
# fails on exactly the machines that are up to date.
if "user_collection_articles" in sa.inspect(op.get_bind()).get_table_names():
return
op.create_table(
"user_collection_articles",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("collection_id", sa.Integer(),
sa.ForeignKey("user_collections.id", ondelete="CASCADE"), nullable=False),
sa.Column("article_id", sa.Integer(),
sa.ForeignKey("articles.id", ondelete="CASCADE"), nullable=False),
sa.UniqueConstraint("collection_id", "article_id", name="uq_collection_article"),
)
op.create_index("ix_user_collection_articles_collection",
"user_collection_articles", ["collection_id"])
def downgrade():
if "user_collection_articles" not in sa.inspect(op.get_bind()).get_table_names():
return
op.drop_index("ix_user_collection_articles_collection", table_name="user_collection_articles")
op.drop_table("user_collection_articles")