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
44 lines
1.8 KiB
Python
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")
|