plain-postgres changelog
0.84.0 (2026-03-12)
What's changed
- Renamed package from
plain-modelstoplain-postgres— the pip package, module path, and package label (plainmodelstoplainpostgres) all reflect the PostgreSQL-only scope. - All internal imports updated from
plain.modelstoplain.postgres. - Flattened
plain.models.postgressubpackage into top-levelplain.postgres.
Upgrade instructions
- Update imports:
from plain.modelstofrom plain.postgres,from plain import modelstofrom plain import postgres. - In
pyproject.toml, changeplain-modelstoplain-postgresandplain.modelstoplain.postgresin dependencies. - In
INSTALLED_PACKAGES, change"plain.models"to"plain.postgres".
0.83.0 (2026-03-12)
What's changed
- Renamed
plain.modelstoplain.postgres— the package name now reflects its PostgreSQL-only scope. - Flattened the
plain.models.postgressubpackage — all internal modules now live at the top level.
Upgrade instructions
- Change
from plain import models→from plain import postgresand update allmodels.Xusages topostgres.X(e.g.postgres.Model,postgres.register_model,postgres.CASCADE). - Change
from plain.models import ...→from plain.postgres import .... - In
pyproject.toml, changeplain.models→plain.postgresandplain-models→plain-postgresin dependencies. - In
INSTALLED_PACKAGES, change"plain.models"→"plain.postgres".
0.82.3 (2026-03-10)
What's changed
- Removed
type: ignorecomments onPOSTGRES_PASSWORDdefault values, now thatSecretis type-transparent (997afd9a558f) - Adopted PEP 695 type parameter syntax across
Field,QuerySet,register_model, type stubs, and other generics (aa5b2db6e8ed) - Added migration docs reminder to AI rules (09deb5d5a382)
Upgrade instructions
- No changes required.
0.82.2 (2026-03-10)
What's changed
- Updated all README code examples to use
types.*with Python type annotations as the default pattern (772345d4e1f1) - Removed separate "Typed fields" and "Typing reverse relationships" doc sections — typed fields are now the default in all examples (772345d4e1f1)
- Added "Field Imports" section and "Differences from Django" section to AI rules (772345d4e1f1)
- Broadened AI rules to apply to all Python files, not just model files (772345d4e1f1)
Upgrade instructions
- No changes required.
0.82.1 (2026-03-10)
What's changed
- Replaced
SET()closure with a_SetOnDeleteclass to eliminatetype: ignorecomments for dynamic attribute assignment (deconstruct,lazy_sub_objs) (cda461b1b4f6) - Replaced
lazy_sub_objsfunction attribute onSET_NULLandSET_DEFAULTwith a module-level_LAZY_ON_DELETEset (cda461b1b4f6) - Narrowed
_relation_treetype and usedget_forward_fieldin migration operations (eb5af6a525b5) - Type annotation improvements across expressions, indexes, related fields, and deletion modules (f56c6454b164)
Upgrade instructions
- No changes required.
0.82.0 (2026-03-09)
What's changed
- Added
EncryptedTextFieldandEncryptedJSONFieldfor transparent encryption at rest using Fernet (AES-128-CBC + HMAC-SHA256) with keys derived fromSECRET_KEY(73f3534f9334) - Encrypted fields support key rotation via
SECRET_KEY_FALLBACKSand gradual migration from plaintext columns (73f3534f9334) - Preflight checks prevent encrypted fields from being used in indexes or constraints (73f3534f9334)
Upgrade instructions
- No changes required. Install the
cryptographypackage to use the new encrypted fields.
0.81.1 (2026-03-09)
What's changed
- Use
connection.execute()instead of opening a cursor for internal one-off queries (timezone configuration, role assumption, connection health checks) (828d665979df)
Upgrade instructions
- No changes required.
0.81.0 (2026-03-09)
What's changed
- psycopg3
cursor.stream()for iterator queries —QuerySet.iterator()now uses psycopg3's native server-side streaming instead offetchmany()chunking, reducing memory overhead for large result sets (49f4d1d996b4) - Minimum PostgreSQL 16 enforced — a preflight check now validates the connected PostgreSQL version is 16 or higher (e1f21c4b251a)
- Renamed
DatabaseWrapper→DatabaseConnectionand moved frompostgres/wrapper.pytopostgres/connection.pyto better reflect the class's purpose (7f17a96a7f8e, 4a79279d01dd) - Replaced
db_connectionproxy withget_connection()— the statelessDatabaseConnectionproxy class is removed in favor of module-levelget_connection()andhas_connection()functions, giving type checkers direct access to the realDatabaseConnectionclass and eliminating proxy overhead (4a79279d01dd) - Replaced
threading.local()withContextVarfor DB connection storage — database connections are now stored per-context instead of per-thread, enabling proper async support (cc2469b1260a) - Removed
validate_thread_sharing()fromDatabaseConnection— thread sharing validation is no longer needed with ContextVar-based connection storage (3a6d6efd09d2) - Extracted
get_converters()andapply_converters()as standalone functions fromSQLCompilerand added type annotations (ed18d3c97142)
Upgrade instructions
- Replace
from plain.models import db_connectionwithfrom plain.models import get_connection, and changedb_connection.cursor()toget_connection().cursor()(and similar attribute access). - If you imported
DatabaseWrapper, it is nowDatabaseConnectionfromplain.models.postgres.connection. - PostgreSQL 16 or higher is now required.
0.80.0 (2026-02-25)
What's changed
- Replaced the
DATABASEdict setting with individualPOSTGRES_*settings (POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DATABASE,POSTGRES_USER,POSTGRES_PASSWORD, etc.) configurable viaPLAIN_POSTGRES_*environment variables orapp/settings.py(e3c5a32d4da6) DATABASE_URLstill works and takes priority — individual settings are parsed from it automatically (e3c5a32d4da6)- Added
DATABASE_URL=noneto explicitly disable the database (e.g. during Docker builds) (e3c5a32d4da6) - Removed the
AUTOCOMMITconfig setting — Plain always runs with autocommit=True (5dc1995615d9) - Refactored backup client internals with shared
_get_conn_args()and_run()helpers (e3c5a32d4da6)
Upgrade instructions
If you use
DATABASE_URL, no changes are required — it continues to work as before.If you manually defined the
DATABASEdict in settings, replace it with individualPOSTGRES_*settings:# Before DATABASE = {"NAME": "mydb", "USER": "me", "HOST": "localhost"} # After POSTGRES_DATABASE = "mydb" POSTGRES_USER = "me" POSTGRES_HOST = "localhost"The
DATABASEdict key"NAME"is now"DATABASE"internally — update any code that accessedsettings_dict["NAME"]directly.Remove any
AUTOCOMMITsetting from your database config — it is no longer recognized.
0.79.0 (2026-02-24)
What's changed
- Added
plain db drop-unknown-tablescommand to remove database tables not associated with any Plain model (108b0bce59e6) - The unknown-tables preflight warning now suggests running
plain db drop-unknown-tablesinstead of manual SQL (108b0bce59e6)
Upgrade instructions
- No changes required.
0.78.0 (2026-02-16)
What's changed
- PostgreSQL is now the only supported database — MySQL and SQLite backends have been removed (6f3a066bf80f)
- The
ENGINEkey has been removed from theDATABASEsetting — it is no longer needed since PostgreSQL is implicit (6f3a066bf80f) - Database backends consolidated from
backends/base/,backends/postgresql/,backends/mysql/, andbackends/sqlite3/into a singlepostgres/module (6f3a066bf80f) - Removed
DatabaseOperationsindirection layer — compilers are now created directly byQuery.get_compiler()(6f3a066bf80f) - Removed backend feature flags and multi-database conditional code throughout expressions, aggregates, schema editor, and migrations (6f3a066bf80f)
- Installation now recommends
uv add plain.models psycopg[binary]to include the PostgreSQL driver (6f3a066bf80f)
Upgrade instructions
- Remove
"ENGINE"from yourDATABASEsetting — it will be ignored - If you were using MySQL or SQLite, you must migrate to PostgreSQL
- Update any imports from
plain.models.backends.baseorplain.models.backends.postgresqltoplain.models.postgres - Install a PostgreSQL driver if you haven't already:
uv add psycopg[binary]
0.77.1 (2026-02-13)
What's changed
- Added migration development workflow documentation covering how to consolidate uncommitted and committed migrations (0b30f98b5346)
- Added migration cleanup guidance to agent rules: consolidate before committing, use squash only for deployed migrations (0b30f98b5346)
Upgrade instructions
- No changes required.
0.77.0 (2026-02-13)
What's changed
makemigrations --dry-runnow shows a SQL preview of the statements each migration would execute, making it easier to review schema changes before writing migration files (c994703f9a28)makemigrationsnow warns when packages have models but nomigrations/directory, which can cause "No changes detected" confusion for new apps (c994703f9a28)- Restructured README documentation: consolidated Querying section with Custom QuerySets, Typing, and Raw SQL; added N+1 avoidance and query efficiency subsections; reorganized Relationships and Constraints into clearer sections with schema design guidance (f5d2731ebda0, 8c2189a896d2)
- Slimmed agent rules to concise bullet reminders with
paths:scoping for**/models.pyfiles (f5d2731ebda0)
Upgrade instructions
- No changes required.
0.76.5 (2026-02-12)
What's changed
- Updated README model validation example to use
@models.register_model,UniqueConstraint, andmodel_options(9db8e0aa5d43) - Added schema planning guidance to agent rules (eaf55cb1b893)
Upgrade instructions
- No changes required.
0.76.4 (2026-02-04)
What's changed
- Added
__all__exports toexpressionsmodule for explicit public API boundaries (e7164d3891b2) - Refactored internal imports to use explicit module paths instead of the
sqlnamespace (e7164d3891b2) - Updated agent rules to use
--apiinstead of--symbolsforplain docscommand (e7164d3891b2)
Upgrade instructions
- No changes required.
0.76.3 (2026-02-02)
What's changed
- Fixed observer query summaries for SQL statements starting with parentheses (e.g., UNION queries) by stripping leading
(before extracting the operation (bfbcb5a256f2) - UNION queries now display with a "UNION" suffix in query summaries for better identification (bfbcb5a256f2)
- Agent rules now include query examples showing the
Model.querypattern (02e11328dbf5)
Upgrade instructions
- No changes required.
0.76.2 (2026-01-28)
What's changed
- Converted the
plain-modelsskill to a passive.claude/rules/file (512040ac51)
Upgrade instructions
- Run
plain agent installto update your.claude/directory.
0.76.1 (2026-01-28)
What's changed
- Added Settings section to README (803fee1ad5)
Upgrade instructions
- No changes required.
0.76.0 (2026-01-22)
What's changed
- Removed the
db_columnfield parameter - column names are now always derived from the field name (eed1bb6) - Removed the
db_collationfield parameter fromCharFieldandTextField- use raw SQL or database-level collation settings instead (49b362d) - Removed the
Collatedatabase function fromplain.models.functions(49b362d) - Removed the
db_commentfield parameter anddb_table_commentmodel option - database comments are no longer supported (eb5aabb) - Removed the
AlterModelTableCommentmigration operation (eb5aabb) - Added
BaseDatabaseSchemaEditorandStateModelsRegistryexports fromplain.models.migrationsfor use in type annotations inRunPythonfunctions (672aa88)
Upgrade instructions
- Remove any
db_columnarguments from field definitions - the column name will always match the field's attribute name (with_idsuffix for foreign keys) - Remove
db_columnfrom all migrations - Remove any
db_collationarguments fromCharFieldandTextFielddefinitions - Replace any usage of
Collate()function with raw SQL queries or configure collation at the database level - Remove any
db_commentarguments from field definitions - Remove
db_commentfrom all migrations - Remove any
db_table_commentfrommodel_optionsdefinitions - Replace
AlterModelTableCommentmigration operations withRunSQLif database comments are still needed
0.75.0 (2026-01-15)
What's changed
- Added type annotations to
CursorWrapperfetch methods (fetchone,fetchmany,fetchall) for better type checker support (7635258) - Internal cleanup: removed redundant
tzinfoclass attribute fromTruncBase(0cb5a84)
Upgrade instructions
- No changes required
0.74.0 (2026-01-15)
What's changed
- Internal skill configuration update - no user-facing changes (fac8673)
Upgrade instructions
- No changes required
0.73.0 (2026-01-15)
What's changed
- The
__repr__method on models now returns<ClassName: id>instead of<ClassName: str(self)>, avoiding potential side effects from custom__str__implementations (0fc4dd3)
Upgrade instructions
- No changes required
0.72.0 (2026-01-13)
What's changed
- Fixed
TimezoneFielddeconstruct path to correctly resolve toplain.modelsinstead ofplain.models.fields.timezones, preventing migration churn when usingTimezoneField(03cc263)
Upgrade instructions
- No changes required
0.71.0 (2026-01-13)
What's changed
TimeZoneFieldchoices are no longer serialized in migrations, preventing spurious migration diffs when timezone data differs between machines (0ede3aae)TimeZoneFieldno longer accepts custom choices - the field's purpose is to provide the canonical timezone list (0ede3aae)- Simplified
plain migrateoutput - package name is only shown when explicitly targeting a specific package (006efae9) - Field ordering is now explicit (primary key first, then alphabetically by name) instead of using an internal creation counter (3ffa44bd)
Upgrade instructions
- If you have existing migrations that contain
TimeZoneFieldwith serializedchoices, you can safely remove thechoicesparameter from those migrations as they are now computed dynamically - If you were passing custom
choicestoTimeZoneField, this is no longer supported - use a regularCharFieldwith choices instead
0.70.0 (2025-12-26)
What's changed
- Added
TimeZoneFieldfor storing timezone information - stores timezone names as strings in the database but provideszoneinfo.ZoneInfoobjects when accessed, similar to howDateFieldworks withdatetime.date(b533189) - Documentation improvements listing all available field types in the README (11837ad)
Upgrade instructions
- No changes required
0.69.1 (2025-12-22)
What's changed
Upgrade instructions
- No changes required
0.69.0 (2025-12-12)
What's changed
- The
queryset.all()method now preserves the prefetch cache, fixing an issue where accessing prefetched related objects through.all()would trigger additional database queries instead of using the cached results (8b899a8)
Upgrade instructions
- No changes required
0.68.0 (2025-12-09)
What's changed
- Database backups now store git metadata (branch and commit) and the
plain db backups listcommand displays this information along with source and size in a table format (287fa89f) - Added
--branchoption toplain db backups listto filter backups by git branch (287fa89f) ReverseForeignKeyandReverseManyToManynow support an optional second type parameter for custom QuerySet types, enabling type checkers to recognize custom QuerySet methods on reverse relations (487c6195)- Internal cleanup: removed legacy generic foreign key related code (c9ca1b67)
Upgrade instructions
- To get type checking for custom QuerySet methods on reverse relations, you can optionally add a second type parameter:
books: types.ReverseForeignKey[Book, BookQuerySet] = types.ReverseForeignKey(to="Book", field="author"). This is optional and existing code without the second parameter continues to work.
0.67.0 (2025-12-05)
What's changed
- Simplified Query/Compiler architecture by moving compiler selection from Query classes to DatabaseOperations (1d1ae5a6)
- The
raw()method now accepts anySequencefor params (e.g., lists) instead of requiring tuples (1d1ae5a6) - Internal type annotation improvements across database backends and SQL compiler modules (bc02184d, e068dcf2, 33fa09d6)
Upgrade instructions
- No changes required
0.66.0 (2025-12-05)
What's changed
- Removed
union(),intersection(), anddifference()combinator methods from QuerySet - use raw SQL for set operations instead (0bae6abd) - Removed
dates()anddatetimes()methods from QuerySet (62ba81a6) - Removed
in_bulk()method from QuerySet (62ba81a6) - Removed
contains()method from QuerySet (62ba81a6) - Internal cleanup: removed unused database backend feature flags and operations (
autoinc_sql,allows_group_by_selected_pks_on_model,connection_persists_old_columns,implied_column_null,for_update_after_from,select_for_update_of_column,modify_insert_params) (defe5015, 7e62b635, 30073da1)
Upgrade instructions
- Replace any usage of
queryset.union(other_qs),queryset.intersection(other_qs), orqueryset.difference(other_qs)with raw SQL queries usingModel.query.raw()or database cursors - Replace
queryset.dates(field, kind)with equivalent annotate/values_list queries usingTruncandDateField - Replace
queryset.datetimes(field, kind)with equivalent annotate/values_list queries usingTruncandDateTimeField - Replace
queryset.in_bulk(id_list)with a dictionary comprehension like{obj.id: obj for obj in queryset.filter(id__in=id_list)} - Replace
queryset.contains(obj)withqueryset.filter(id=obj.id).exists()
0.65.1 (2025-12-04)
What's changed
- Fixed type annotations for
get_rhs_opmethod in lookup classes to acceptstr | list[str]parameter, resolving type checker errors when usingRangeand other lookups that return list-based RHS values (7030cd0)
Upgrade instructions
- No changes required
0.65.0 (2025-12-04)
What's changed
- Improved type annotations for
ReverseForeignKeyandReverseManyToManydescriptors - they are now proper generic descriptor classes with__get__overloads, providing better type inference when accessed on class vs instance (ac1eeb0) - Internal type annotation improvements across aggregates, expressions, database backends, and SQL compiler modules (ac1eeb0)
Upgrade instructions
- No changes required
0.64.0 (2025-11-24)
What's changed
bulk_create()andbulk_update()now accept anySequencetype (e.g., tuples, generators) instead of requiring alist(6c7469f)
Upgrade instructions
- No changes required
0.63.1 (2025-11-21)
What's changed
- Fixed
ManyToManyFieldpreflight checks that could fail when the intermediate model contained non-related fields (e.g.,CharField,IntegerField) by properly filtering to only checkRelatedFieldinstances when counting foreign keys (4a3fe5d)
Upgrade instructions
- No changes required
0.63.0 (2025-11-21)
What's changed
ForeignKeyhas been renamed toForeignKeyFieldfor consistency with other field naming conventions (8010204)- Improved type annotations for
ManyToManyField- now returnsManyToManyManager[T]instead ofAnyfor better IDE support (4536097) - Related managers (
ReverseForeignKeyManagerandManyToManyManager) are now generic classes with proper type parameters for improved type checking (3f61b6e) - Added
ManyToManyManagerandReverseForeignKeyManagerexports toplain.models.typesfor use in type annotations (4536097)
Upgrade instructions
- Replace all usage of
models.ForeignKeywithmodels.ForeignKeyField(e.g.,category = models.ForeignKey("Category", on_delete=models.CASCADE)becomescategory = models.ForeignKeyField("Category", on_delete=models.CASCADE)) - Replace all usage of
types.ForeignKeywithtypes.ForeignKeyFieldin typed model definitions - Update migrations to use
ForeignKeyFieldinstead ofForeignKey
0.62.1 (2025-11-20)
What's changed
- Fixed a bug where non-related fields could cause errors in migrations and schema operations by incorrectly assuming all fields have a
remote_fieldattribute (60b1bcc)
Upgrade instructions
- No changes required
0.62.0 (2025-11-20)
What's changed
- The
namedparameter has been removed fromQuerySet.values_list()- named tuples are no longer supported for values lists (0e39711) - Internal method
get_extra_restriction()has been removed from related fields and query data structures (6157bd9) - Internal helper function
get_model_meta()has been removed in favor of direct attribute access (cb5a50e) - Extensive type annotation improvements across the entire package, including database backends, query compilers, fields, migrations, and SQL modules (a43145e)
- Added
isinstancechecks for related fields and improved type narrowing throughout the codebase (5b4bdf4) - Improved type annotations for
Options.get_fields()and related meta methods with more specific return types (2c26f86)
Upgrade instructions
- Remove any usage of the
named=Trueparameter invalues_list()calls - if you need named access to query results, use.values()which returns dictionaries instead
0.61.1 (2025-11-17)
What's changed
- The
@dataclass_transformdecorator has been removed fromModelBaseto avoid type checker issues (e0dbedb) - Documentation and examples no longer suggest using
ClassVarfor QuerySet type annotations - the simplerquery: models.QuerySet[Model] = models.QuerySet()pattern is now recommended (1c624ff, 99aecbc)
Upgrade instructions
- If you were using
ClassVarannotations for thequeryattribute, you can optionally remove theClassVarwrapper and thefrom typing import ClassVarimport. Both patterns work, but the simpler version withoutClassVaris now recommended.
0.61.0 (2025-11-14)
What's changed
- The
related_nameparameter has been removed fromForeignKeyandManyToManyField- reverse relationships are now declared explicitly usingReverseForeignKeyandReverseManyToManydescriptors on the related model (a4b630969d) - Added
ReverseForeignKeyandReverseManyToManydescriptor classes toplain.models.typesfor declaring reverse relationships with full type support (a4b630969d) - The new reverse descriptors are exported from
plain.modelsfor easy access (97fa112975) - Renamed internal references from
ManyToOnetoForeignKeyfor consistency (93c30f9caf) - Fixed a preflight check bug related to reverse relationships (9191ae6e4b)
- Added comprehensive documentation for reverse relationships in the README (5abf330e06)
Upgrade instructions
- Remove all
related_nameparameters fromForeignKeyandManyToManyFielddefinitions - Remove
related_namefrom all migrations - On the related model, add explicit reverse relationship descriptors using
ReverseForeignKeyorReverseManyToManyfromplain.models.types:- For the reverse side of a
ForeignKey, use:children: types.ReverseForeignKey[Child] = types.ReverseForeignKey(to="Child", field="parent") - For the reverse side of a
ManyToManyField, use:cars: types.ReverseManyToMany[Car] = types.ReverseManyToMany(to="Car", field="features")
- For the reverse side of a
- Remove any
TYPE_CHECKINGblocks that were used to declare reverse relationship types - the new descriptors provide full type support without these hacks - The
toparameter accepts either a string (model name) or the model class itself - The
fieldparameter should be the name of the forward field on the related model
0.60.0 (2025-11-13)
What's changed
- Type annotations for QuerySets using
ClassVarto improve type checking when accessingModel.query(c3b00a6) - The
idfield on the Model base class now uses a type annotation (id: int = types.PrimaryKeyField()) for better type checking (9febc80) - Replaced wildcard imports (
import *) with explicit imports in internal modules for better code clarity (eff36f3)
Upgrade instructions
- Optionally (but recommended) add
ClassVartype annotations to custom QuerySets on your models usingquery: ClassVar[models.QuerySet[YourModel]] = models.QuerySet()for improved type checking and IDE autocomplete
0.59.1 (2025-11-13)
What's changed
- Added documentation for typed field definitions in the README, showing examples of using
plain.models.typeswith type annotations (f95d32d)
Upgrade instructions
- Optionally (but recommended) move to typed model field definitions by using
name: str = types.CharField(...)instead ofname = models.CharField(...). Types can be imported withfrom plain.models import types.
0.59.0 (2025-11-13)
What's changed
- Added a new
plain.models.typesmodule with type stub support (.pyi) for improved IDE and type checker experience when defining models (c8f40fc) - Added
@dataclass_transformdecorator toModelBaseto enable better type checking for model field definitions (c8f40fc)
Upgrade instructions
- No changes required
0.58.0 (2025-11-12)
What's changed
- Internal base classes have been converted to use Python's ABC (Abstract Base Class) module with
@abstractmethoddecorators, improving type checking and making the codebase more maintainable (b1f40759, 7146cabc, 74f9a171, b647d156, 6f3e35d9, 95620673, 7ff5e98c, 78323300, df82434d, 16350d98, 066eaa4b, 60fabefa, 9f822ccc, 6b31752c) - Type annotations have been improved across database backends, query compilers, and migrations for better IDE support (f4dbcefa, dc182c2e)
Upgrade instructions
- No changes required
0.57.0 (2025-11-11)
What's changed
- The
plain.modelsimport namespace has been cleaned up to only include the most commonly used APIs for defining models (e9edf61, 22b798c, d5a2167) - Field classes are now descriptors themselves, eliminating the need for a separate descriptor class (93f8bd7)
- Model initialization no longer accepts positional arguments - all field values must be passed as keyword arguments (685f99a)
- Attempting to set a primary key during model initialization now raises a clear
ValueErrorinstead of silently accepting the value (ecf490c)
Upgrade instructions
- Import advanced query features from their specific modules instead of
plain.models:- Aggregates:
from plain.models.aggregates import Avg, Count, Max, Min, Sum - Expressions:
from plain.models.expressions import Case, Exists, Expression, ExpressionWrapper, F, Func, OuterRef, Subquery, Value, When, Window - Query utilities:
from plain.models.query import Prefetch, prefetch_related_objects - Lookups:
from plain.models.lookups import Lookup, Transform
- Aggregates:
- Remove any positional arguments in model instantiation and use keyword arguments instead (e.g.,
User("John", "Doe")becomesUser(first_name="John", last_name="Doe"))
0.56.1 (2025-11-03)
What's changed
- Fixed preflight checks and README to reference the correct new command names (
plain db shellandplain migrations prune) instead of the oldplain modelscommands (b293750)
Upgrade instructions
- No changes required
0.56.0 (2025-11-03)
What's changed
- The CLI has been reorganized into separate
plain dbandplain migrationscommand groups for better organization (7910a06) - The
plain modelscommand group has been removed - useplain dbandplain migrationsinstead (7910a06) - The
plain backupscommand group has been removed - useplain db backupsinstead (dd87b76) - Database backup output has been simplified to show file size and timestamp on a single line (765d118)
Upgrade instructions
- Replace
plain models db-shellwithplain db shell - Replace
plain models db-waitwithplain db wait - Replace
plain models listwithplain db list(note: this command was moved to the main plain package) - Replace
plain models show-migrationswithplain migrations list - Replace
plain models prune-migrationswithplain migrations prune - Replace
plain models squash-migrationswithplain migrations squash - Replace
plain backupscommands withplain db backups(e.g.,plain backups listbecomesplain db backups list) - The shortcuts
plain makemigrationsandplain migratecontinue to work unchanged
0.55.1 (2025-10-31)
What's changed
- Added
license = "BSD-3-Clause"to package metadata (8477355)
Upgrade instructions
- No changes required
0.55.0 (2025-10-24)
What's changed
- The plain-models package now uses an explicit
package_label = "plainmodels"to avoid conflicts with other packages (d1783dd) - Fixed migration loader to correctly check for
plainmodelspackage label instead ofmodels(c41d11c)
Upgrade instructions
- No changes required
0.54.0 (2025-10-22)
What's changed
- SQLite migrations are now always run separately instead of in atomic batches, fixing issues with foreign key constraint handling (5082453)
Upgrade instructions
- No changes required
0.53.1 (2025-10-20)
What's changed
- Internal packaging update to use
dependency-groupsstandard instead oftool.uv.dev-dependencies(1b43a3a)
Upgrade instructions
- No changes required
0.53.0 (2025-10-12)
What's changed
- Added new
plain models prune-migrationscommand to identify and remove stale migration records from the database (998aa49) - The
--pruneoption has been removed fromplain migratecommand in favor of the dedicatedprune-migrationscommand (998aa49) - Added new preflight check
models.prunable_migrationsthat warns about stale migration records in the database (9b43617) - The
show-migrationscommand no longer displays prunable migrations in its output (998aa49)
Upgrade instructions
- Replace any usage of
plain migrate --prunewith the newplain models prune-migrationscommand
0.52.0 (2025-10-10)
What's changed
- The
plain migratecommand now shows detailed operation descriptions and SQL statements for each migration step, replacing the previous verbosity levels with a cleaner--quietflag (d6b041bd24) - Migration output format has been improved to display each operation's description and the actual SQL being executed, making it easier to understand what changes are being made to the database (d6b041bd24)
- The
-v/--verbosityoption has been removed fromplain migratein favor of the simpler--quietflag for suppressing output (d6b041bd24)
Upgrade instructions
- Replace any usage of
-vor--verbosityflags inplain migratecommands with--quietif you want to suppress migration output
0.51.1 (2025-10-08)
What's changed
- Fixed a bug in
SubqueryandExistsexpressions that was using the oldqueryattribute name instead ofsql_querywhen extracting the SQL query from a QuerySet (79ca52d)
Upgrade instructions
- No changes required
0.51.0 (2025-10-07)
What's changed
- Model metadata has been split into two separate descriptors:
model_optionsfor user-defined configuration and_model_metafor internal metadata (73ba469, 17a378d) - The
_metaattribute has been replaced withmodel_optionsfor user-defined options like indexes, constraints, and database settings (17a378d) - Custom QuerySets are now assigned directly to the
queryclass attribute instead of usingMeta.queryset_class(2578301) - Added comprehensive type improvements to model metadata and related fields for better IDE support (3b477a0)
Upgrade instructions
- Replace
Meta.queryset_class = CustomQuerySetwithquery = CustomQuerySet()as a class attribute on your models - Replace
class Meta:withmodel_options = models.Options(...)in your models
0.50.0 (2025-10-06)
What's changed
- Added comprehensive type annotations throughout plain-models, improving IDE support and type checking capabilities (ea1a7df, f49ee32, 369353f, 13b7d16, e23a0ca, 02d8551)
- The
QuerySetclass is now generic and themodelparameter is now required in the__init__method (719e792) - Database wrapper classes have been renamed for consistency:
DatabaseWrapperclasses are now namedMySQLDatabaseWrapper,PostgreSQLDatabaseWrapper, andSQLiteDatabaseWrapper(5a39e85) - The plain-models package now has 100% type annotation coverage and is validated in CI to prevent regressions
Upgrade instructions
- No changes required
0.49.2 (2025-10-02)
What's changed
- Updated dependency to use the latest plain package version
Upgrade instructions
- No changes required
0.49.1 (2025-09-29)
What's changed
- Fixed
get_field_display()method to accept field name as string instead of field object (1c20405)
Upgrade instructions
- No changes required
0.49.0 (2025-09-29)
What's changed
- Model exceptions (
FieldDoesNotExist,FieldError,ObjectDoesNotExist,MultipleObjectsReturned,EmptyResultSet,FullResultSet) have been moved fromplain.exceptionstoplain.models.exceptions(1c02564) - The
get_FOO_display()methods for fields with choices have been replaced with a singleget_field_display(field_name)method (e796e71) - The
get_next_by_*andget_previous_by_*methods for date fields have been removed (3a5b8a8) - The
idprimary key field is now defined directly on the Model base class instead of being added dynamically via Options (e164dc7) - Model
DoesNotExistandMultipleObjectsReturnedexceptions now use descriptors for better performance (8f54ea3)
Upgrade instructions
- Update imports for model exceptions from
plain.exceptionstoplain.models.exceptions(e.g.,from plain.exceptions import ObjectDoesNotExistbecomesfrom plain.models.exceptions import ObjectDoesNotExist) - Replace any usage of
instance.get_FOO_display()withinstance.get_field_display("FOO")where FOO is the field name - Remove any usage of
get_next_by_*andget_previous_by_*methods - use QuerySet ordering instead (e.g.,Model.query.filter(date__gt=obj.date).order_by("date").first())
0.48.0 (2025-09-26)
What's changed
- Migrations now run in a single transaction by default for databases that support transactional DDL, providing all-or-nothing migration batches for better safety and consistency (6d0c105)
- Added
--atomic-batch/--no-atomic-batchoptions toplain migrateto explicitly control whether migrations are run in a single transaction (6d0c105)
Upgrade instructions
- No changes required
0.47.0 (2025-09-25)
What's changed
- The
QuerySet.queryproperty has been renamed toQuerySet.sql_queryto better distinguish it from theModel.querymanager interface (d250eea)
Upgrade instructions
- If you directly accessed the
QuerySet.queryproperty in your code (typically for advanced query manipulation or debugging), rename it toQuerySet.sql_query
0.46.1 (2025-09-25)
What's changed
- Fixed
prefetch_relatedfor reverse foreign key relationships by correctly handling related managers in the prefetch query process (2c04e80)
Upgrade instructions
- No changes required
0.46.0 (2025-09-25)
What's changed
- The preflight system has been completely reworked with a new
PreflightResultclass that unifies messages and hints into a singlefixfield, providing clearer and more actionable error messages (b0b610d, c7cde12) - Preflight check IDs have been renamed to use descriptive names instead of numbers for better clarity (e.g.,
models.E003becomesmodels.duplicate_many_to_many_relations) (cd96c97) - Removed deprecated field types:
CommaSeparatedIntegerField,IPAddressField, andNullBooleanField(345295dc) - Removed
system_check_deprecated_detailsandsystem_check_removed_detailsfrom fields (e3a7d2dd)
Upgrade instructions
- Remove any usage of the deprecated field types
CommaSeparatedIntegerField,IPAddressField, andNullBooleanField- useCharField,GenericIPAddressField, andBooleanField(null=True)respectively
0.45.0 (2025-09-21)
What's changed
- Added unlimited varchar support to SQLite - CharField fields without a max_length now generate
varcharcolumns instead ofvarchar()with no length specified (c5c0c3a)
Upgrade instructions
- No changes required
0.44.0 (2025-09-19)
What's changed
- PostgreSQL backup restoration now drops and recreates the database instead of using
pg_restore --clean, providing more reliable restoration by terminating active connections and ensuring a completely clean database state (a8865fe) - Added
_metatype annotation to theModelclass for improved type checking and IDE support (387b92e)
Upgrade instructions
- No changes required
0.43.0 (2025-09-12)
What's changed
- The
related_nameparameter is now required for ForeignKey and ManyToManyField relationships if you want a reverse accessor. The"+"suffix to disable reverse relations has been removed, and automatic_setsuffixes are no longer generated (89fa03979f) - Refactored related descriptors and managers for better internal organization and type safety (9f0b03957a)
- Added docstrings and return type annotations to model
queryproperty and related manager methods for improved developer experience (544d85b60b)
Upgrade instructions
- Remove any
related_name="+"usage - if you don't want a reverse accessor, simply omit therelated_nameparameter entirely - Update any code that relied on automatic
_setsuffixes - these are no longer generated, so you must use explicitrelated_namevalues - Add explicit
related_namearguments to all ForeignKey and ManyToManyField definitions where you want reverse access (e.g.,models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles")) - Consider removing
related_namearguments that are not used in practice
0.42.0 (2025-09-12)
What's changed
- The model manager interface has been renamed from
.objectsto.query(037a239) - Manager functionality has been merged into QuerySet, simplifying the architecture - custom QuerySets can now be set directly via
Meta.queryset_class(bbaee93) - The
objectsmanager is now set directly on the Model class for better type checking (fccc5be) - Database backups are now created automatically during migrations when in DEBUG mode (c8023074)
- Removed several legacy manager features:
default_related_name,base_manager_name,creation_counter,use_in_migrations,auto_created, and routing hints (multiple commits)
Upgrade instructions
- Replace all usage of
Model.objectswithModel.queryin your codebase (e.g.,User.objects.filter()becomesUser.query.filter()) - If you have custom managers, convert them to custom QuerySets and set them using
Meta.queryset_classinstead of assigning to class attributes (if there is more than one custom manager on a class, invoke the new QuerySet class directly or add a shortcut on the Model using@classmethod) - Remove any usage of the removed manager features:
default_related_name,base_manager_name, managercreation_counter,use_in_migrations,auto_created, and database routing hints - Any reverse accessors (typically
<related_model>_setor defined byrelated_name) will now return a manager class for the additionaladd(),remove(),clear(), etc. methods and the regular queryset methods will be available via.query(e.g.,user.articles.first()becomesuser.articles.query.first())
0.41.1 (2025-09-09)
What's changed
- Improved stack trace filtering in OpenTelemetry spans to exclude internal plain/models frames, making debugging traces cleaner and more focused on user code (5771dd5)
Upgrade instructions
- No changes required
0.41.0 (2025-09-09)
What's changed
- Python 3.13 is now the minimum required version (d86e307)
- Removed the
earliest(),latest(), andget_latest_bymodel meta option - useorder_by().first()andorder_by().last()instead (b6093a8) - Removed automatic ordering in
first()andlast()queryset methods - they now respect the existing queryset ordering without adding default ordering (adc19a6) - Added code location attributes to database operation tracing, showing the source file, line number, and function where the query originated (da36a17)
Upgrade instructions
- Replace usage of
earliest(),latest(), and modelMetaget_latest_byqueryset methods with equivalentorder_by().first()ororder_by().last()calls - The
first()andlast()methods no longer automatically add ordering byid- explicitly add.order_by()to your querysets ororderingto your modelsMetaclass if needed
0.40.1 (2025-09-03)
What's changed
- Internal documentation updates for agent commands (df3edbf0bd)
Upgrade instructions
- No changes required
0.40.0 (2025-08-05)
What's changed
- Foreign key fields now accept lazy objects (like
SimpleLazyObjectused forrequest.user) by automatically evaluating them (eb78dcc76d) - Added
--no-inputoption toplain migratecommand to skip user prompts (0bdaf0409e) - Removed the
plain models optimize-migrationcommand (6e4131ab29) - Removed the
--fake-initialoption fromplain migratecommand (6506a8bfb9) - Fixed CLI help text to reference
plaincommands instead ofmanage.py(8071854d61)
Upgrade instructions
- Remove any usage of
plain models optimize-migrationcommand - it is no longer available - Remove any usage of
--fake-initialoption fromplain migratecommands - it is no longer supported - It is no longer necessary to do
user=request.user or None, for example, when setting foreign key fields with a lazy object likerequest.user. These will now be automatically evaluated.
0.39.2 (2025-07-25)
What's changed
- Fixed remaining
to_field_nameattribute usage inModelMultipleChoiceFieldvalidation to useiddirectly (26c80356d3)
Upgrade instructions
- No changes required
0.39.1 (2025-07-22)
What's changed
- Added documentation for sharing fields across models using Python class mixins (cad3af01d2)
- Added note about
PrimaryKeyField()replacement requirement for migrations (70ea931660)
Upgrade instructions
- No changes required
0.39.0 (2025-07-22)
What's changed
- Models now use a single automatic
idfield as the primary key, replacing the previouspkalias and automatic field system (4b8fa6a) - Removed the
to_fieldoption for ForeignKey - foreign keys now always reference the primary key of the related model (7fc3c88) - Removed the internal
from_fieldsandto_fieldssystem used for multi-column foreign keys (0e9eda3) - Removed the
parent_linkparameter on ForeignKey and ForeignObject (6658647) - Removed
InlineForeignKeyFieldfrom forms (ede6265) - Merged ForeignObject functionality into ForeignKey, simplifying the foreign key implementation (e6d9aaa)
- Cleaned up unused code in ForeignKey and fixed ForeignObjectRel imports (b656ee6)
Upgrade instructions
- Replace any direct references to
pkwithidin your models and queries (e.g.,user.pkbecomesuser.id) - Remove any
to_fieldarguments from ForeignKey definitions - they are no longer supported - Remove any
parent_link=Truearguments from ForeignKey definitions - they are no longer supported - Replace any usage of
InlineForeignKeyFieldin forms with standard form fields models.BigAutoField(auto_created=True, primary_key=True)need to be replaced withmodels.PrimaryKeyField()in migrations
0.38.0 (2025-07-21)
What's changed
- Added
get_or_none()method to QuerySet which returns a single object matching the given arguments or None if no object is found (48e07bf)
Upgrade instructions
- No changes required
0.37.0 (2025-07-18)
What's changed
- Added OpenTelemetry instrumentation for database operations - all SQL queries now automatically generate OpenTelemetry spans with standardized attributes following semantic conventions (b0224d0)
- Database operations in tests are now wrapped with tracing suppression to avoid generating telemetry noise during test execution (b0224d0)
Upgrade instructions
- No changes required
0.36.0 (2025-07-18)
What's changed
- Removed the
--mergeoption from themakemigrationscommand (d366663) - Improved error handling in the
restore-backupcommand using Click's error system (88f06c5)
Upgrade instructions
- No changes required
0.35.0 (2025-07-07)
What's changed
- Added the
plain models listCLI command which prints a nicely formatted list of all installed models, including their table name, fields, and originating package. You can pass package labels to filter the output or use the--app-onlyflag to only show first-party app models (1bc40ce). - The MySQL backend no longer enforces a strict
mysqlclient >= 1.4.3version check and had several unused constraint-handling methods removed, reducing boilerplate and improving compatibility with a wider range ofmysqlclientversions (6322400, 67f21f6).
Upgrade instructions
- No changes required
0.34.4 (2025-07-02)
What's changed
- The built-in
on_deletebehaviors (CASCADE,PROTECT,RESTRICT,SET_NULL,SET_DEFAULT, and the callables returned bySET(...)) no longer receive the legacyusingargument. Their signatures are now(collector, field, sub_objs)(20325a1). - Removed the unused
interprets_empty_strings_as_nullsbackend feature flag and the related fallback logic (285378c).
Upgrade instructions
- No changes required
0.34.3 (2025-06-29)
What's changed
- Simplified log output when creating or destroying test databases during test setup. The messages now display the test database name directly and no longer reference the deprecated "alias" terminology (a543706).
Upgrade instructions
- No changes required
0.34.2 (2025-06-27)
What's changed
- Fixed PostgreSQL
_nodb_cursorfallback that could raiseTypeError: __init__() got an unexpected keyword argument 'alias'when the maintenance database wasn't available (3e49683). - Restored support for the
USINGclause when creating PostgreSQL indexes; custom index types such asGINandGISTare now generated correctly again (9d2b8fe).
Upgrade instructions
- No changes required
0.34.1 (2025-06-23)
What's changed
- Fixed Markdown bullet indentation in the 0.34.0 release notes so they render correctly (2fc81de).
Upgrade instructions
- No changes required
0.34.0 (2025-06-23)
What's changed
- Switched to a single
DATABASEsetting instead ofDATABASESand removedDATABASE_ROUTERS. A helper still automatically populatesDATABASEfromDATABASE_URLjust like before (d346d81). - The
plain.models.dbmodule now exposes adb_connectionobject that lazily represents the active database connection. Previousconnections,router, andDEFAULT_DB_ALIASexports were removed (d346d81).
Upgrade instructions
- Replace any
DATABASESdefinition in your settings with a singleDATABASEdict (keys are identical to the inner dict you were previously using). - Remove any
DATABASE_ROUTERSconfiguration – multiple databases are no longer supported. - Update import sites:
from plain.models import connections→from plain.models import db_connectionfrom plain.models import router→ (no longer needed; remove usage or switch todb_connectionwhere appropriate)from plain.models.connections import DEFAULT_DB_ALIAS→ (constant removed; default database is implicit)