plain.postgres 0.115.0__tar.gz → 0.117.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/PKG-INFO +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/CHANGELOG.md +46 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/base.py +44 -66
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/connection.py +1 -3
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/constraints.py +2 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/convergence/analysis.py +3 -8
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/expressions.py +14 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/base.py +11 -24
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/binary.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/encrypted.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/json.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/network.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/numeric.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/primary_key.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/related.py +57 -56
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/related_managers.py +4 -9
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/reverse_related.py +50 -18
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/temporal.py +1 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/text.py +2 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/timezones.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/uuid.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/forms.py +2 -4
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/ownership.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/meta.py +40 -96
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/autodetector.py +23 -23
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/graph.py +7 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/state.py +24 -20
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/utils.py +7 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/preflight/indexes.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/query.py +16 -24
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/query_utils.py +5 -5
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/schema.py +1 -1
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/compiler.py +2 -2
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/datastructures.py +3 -3
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/query.py +8 -12
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/pyproject.toml +1 -1
- plain_postgres-0.117.0/tests/internal/test_unresolved_relation_refs.py +144 -0
- plain_postgres-0.117.0/tests/public/test_queryset_ordered.py +14 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/.gitignore +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/CLAUDE.md +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/LICENSE +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/README.md +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/README.md +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/adapters.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/aggregates.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/converge.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/core.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/decorators.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/diagnose.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/migrations.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/schema.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/cli/sync.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/config.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/constants.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/convergence/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/convergence/corrections.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/convergence/planning.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/database_url.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/databases.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/db.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/ddl.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/default_settings.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/deletion.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/dialect.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/entrypoints.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/enums.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/exceptions.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/boolean.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/duration.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/mixins.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/related_descriptors.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/related_lookups.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/fields/reverse_descriptors.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/comparison.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/datetime.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/math.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/mixins.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/random.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/text.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/uuid.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/functions/window.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/indexes.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/checks_structural.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/context.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/helpers.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/runner.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/health/types.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/introspection/schema.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/lookups.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/middleware.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/exceptions.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/executor.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/loader.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/migration.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/operations/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/operations/base.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/operations/fields.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/operations/models.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/operations/special.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/optimizer.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/questioner.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/recorder.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/serializer.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/migrations/writer.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/options.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/otel.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/preflight/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/preflight/database.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/preflight/models.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/registry.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/schema_lock.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sources.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/constants.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/sql/where.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/test/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/test/database.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/test/pytest.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/transaction.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/types.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/types.pyi +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/plain/postgres/utils.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/forms.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0006_secretstore.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0016_formsexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/0018_storageparametersexample.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/__init__.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/constraints.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/delete.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/encrypted.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/forms.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/indexes.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/iteration.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/mixins.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/nullability.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/querysets.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/relationships.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/storage_parameters.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/trees.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/models/unregistered.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/urls.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/examples/views.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/settings.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/app/urls.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/conftest.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/conftest_convergence.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_apply_replan.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_autodetector_not_null_errors.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_autodetector_type_change.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_connection_isolation.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_connection_lifecycle.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_connection_pool.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_connection_self_heal.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_constraint_violation_error.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_constraints.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_fk.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_indexes.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_nullability.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_storage_parameters.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_convergence_timeouts.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_databases_not_on_runtime_path.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_db_expression_defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_diagnose.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_executor_connection_hook.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_fk_characterization.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_health.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_introspection.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_literal_default_persistence.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_management_connection.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_migration_executor.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_no_callable_defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_otel_metrics.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_preflight_duplicate_indexes.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_preflight_fk_composite_hint.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_preflight_fk_coverage.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_rollback_exc_attribution.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_schema_lock.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_schema_normalize_type.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/internal/test_schema_timeouts.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_create_update.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_database_url.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_databases.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_deferred_loading.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_delete_behaviors.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_encrypted_fields.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_exceptions.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_field_defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_functions_uuid.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_integrity_error_mapping.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_iterator.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_m2m.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_manager_assignment.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_manual_pk.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_mixins.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_modelform_roundtrip.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_only_empty_defaults.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_order_by_expressions.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_queryset_repr.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_queryset_slicing.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_random_string_field.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_raw_query.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_read_only_transactions.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_related.py +0 -0
- {plain_postgres-0.115.0 → plain_postgres-0.117.0}/tests/public/test_related_instance_filter.py +0 -0
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# plain-postgres changelog
|
|
2
2
|
|
|
3
|
+
## [0.117.0](https://github.com/dropseed/plain/releases/plain-postgres@0.117.0) (2026-09-04)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- Fixed `QuerySet.ordered`, which raised `AttributeError: 'Meta' object has no attribute 'ordering'` for any queryset without an explicit `order_by()`. It was reading the model's default ordering off `_model_meta` instead of `model_options`, so the property only ever worked on the one branch that never reached the lookup. It now correctly reports `True` when the model declares `Meta.ordering` and `False` when it doesn't ([d9406dfc6f](https://github.com/dropseed/plain/commit/d9406dfc6f))
|
|
8
|
+
- Unresolved relation references are now separate attributes from resolved model classes. `rel.model` and `ManyToManyRel.through` used to hold whatever you passed to the field — a `"package.Model"` string early on, the class after `lazy_related_operation` overwrote it — so every reader had to guess which it had, and the guessing was done inconsistently (`isinstance(..., str)` in some places, `isinstance(..., ModelBase)` in others, `hasattr(..., "_model_meta")` in others still). The raw reference now lives on `rel.model_ref` and `rel.through_ref`, and `rel.model` / `rel.through` are read-only properties that return the resolved class or raise `TypeError` if it isn't resolved yet. The `TypeError` is deliberate: an `AttributeError` would be swallowed by `hasattr`/`getattr` and silently read as "no model" ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
9
|
+
- `ForeignKeyField.target_field` dropped its own unresolved-reference `TypeError` — the `rel.model` property raises it now, with a message naming the field ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
10
|
+
- `ManyToManyField._check_through_fields` reports an unresolved target by name instead of crashing on `related_model.model_options.object_name`, so a `through_fields` mistake on a not-yet-resolved relation produces the intended preflight message ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
11
|
+
- `ModelState` now raises `TypeError` (was `ValueError`) when a field's `to` or `through` refers to a model class instead of a string reference, and detects it by checking the reference directly rather than probing for `_model_meta` ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
12
|
+
- Removed `Field.concrete`. It was set to `self.column is not None` right after `column` was assigned the field's name, so it was `True` on every forward field and the checks that consulted it were dead. `bulk_create()` and `bulk_update()` now say what they actually reject — "bulk_update() cannot be used with many-to-many fields." rather than "can only be used with concrete fields." — and `Model.save(update_fields=...)` drops "non-concrete fields" from its error message. `ForeignObjectRel.concrete` and the unreachable virtual-field skip in `Model.__init__` are gone too ([af56244677](https://github.com/dropseed/plain/commit/af56244677))
|
|
13
|
+
- `OrderBy.asc()` and `.desc()` return the `OrderBy` instead of `None`, matching every other expression's `asc`/`desc` and making `expr.desc().asc()` and inline use work. They still take no options — passing `nulls_first` or `nulls_last` raises `TypeError` pointing you at the attributes on the `OrderBy` itself, rather than being silently ignored ([f00e707ed0](https://github.com/dropseed/plain/commit/f00e707ed0))
|
|
14
|
+
- Preflight expression checks no longer crash when a lookup path continues past a non-relation field — for example `Meta.ordering = ("name__nope",)` on a text field. The traversal now raises `FieldDoesNotExist` for the trailing part, which preflight already reports, instead of an `AttributeError` on `None` ([d9406dfc6f](https://github.com/dropseed/plain/commit/d9406dfc6f))
|
|
15
|
+
- `MigrationGraph.make_state()` raises `NodeNotFoundError` when the plan hits a placeholder node, instead of an `AttributeError` on `None` ([d9406dfc6f](https://github.com/dropseed/plain/commit/d9406dfc6f))
|
|
16
|
+
- The migration autodetector and `ProjectState` narrow on `RelatedField` / `ManyToManyField` / `ManyToManyRel` instead of `hasattr(field, "remote_field")` and `getattr(remote_field, "through", None)`. Rename handling only rewrites a reference when the old field is the same kind of relation, so a field swapped between a plain relation and an M2M during a rename no longer copies a mismatched reference across ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
17
|
+
- `ProjectState._get_related_models` reads a forward relation's target from `remote_field.model_ref` rather than `f.model` — which returned the model the field is _declared on_, not the one it points at ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
18
|
+
- `Meta._expire_cache` propagation to a related model no longer swallows `AttributeError` to handle the unresolved-string case; it checks `model_ref` up front, so a genuine `AttributeError` inside `_expire_cache` surfaces ([a3962f7255](https://github.com/dropseed/plain/commit/a3962f7255))
|
|
19
|
+
|
|
20
|
+
### Upgrade instructions
|
|
21
|
+
|
|
22
|
+
- If you read `rel.model` or `rel.through` (via `field.remote_field`) in code that runs before models are fully loaded — preflight checks, `contribute_to_class`, custom fields, migration operations — switch to `rel.model_ref` / `rel.through_ref` and check `isinstance(ref, str)`. After model loading completes, `rel.model` and `rel.through` behave as before.
|
|
23
|
+
- If you _assigned_ to `rel.model` or `rel.through`, assign to `rel.model_ref` / `rel.through_ref` instead — the plain names are now read-only properties.
|
|
24
|
+
- If you referenced `Field.concrete` or `ForeignObjectRel.concrete`, remove the check. `concrete` was `True` for every forward field and `False` only on reverse relations, which `get_fields()` doesn't return unless you pass `include_reverse=True`.
|
|
25
|
+
- If you asserted on the exact text of the `bulk_create()`, `bulk_update()`, or `save(update_fields=...)` errors, update the expected strings.
|
|
26
|
+
- If you caught `ValueError` from `ModelState` for a model-class `to`/`through` reference, catch `TypeError`.
|
|
27
|
+
- `OrderBy.asc()`/`.desc()` returning `self` is additive — existing calls that discarded the return value are unaffected.
|
|
28
|
+
|
|
29
|
+
## [0.116.0](https://github.com/dropseed/plain/releases/plain-postgres@0.116.0) (2026-09-04)
|
|
30
|
+
|
|
31
|
+
### What's changed
|
|
32
|
+
|
|
33
|
+
- **`Meta` now exposes exactly two forward-field collections: `fields` and `many_to_many`.** `local_fields`, `local_many_to_many`, `concrete_fields`, and `local_concrete_fields` are gone. Plain has no model inheritance and no non-concrete forward fields, so "local" and "concrete" drew distinctions that could never differ — four names for two lists, each with its own cached property and its own filtering pass. `fields` is every column-backed field (primary key first, then sorted by name) and `many_to_many` is every `ManyToManyField` (sorted by name); both now read straight from the storage `add_field()` fills, instead of re-filtering `_get_fields()` on each access ([9f0d0da22d](https://github.com/dropseed/plain/commit/9f0d0da22d))
|
|
34
|
+
- `Meta.fields` is typed as `ImmutableList[ColumnField]` and `Meta.many_to_many` as `ImmutableList[ManyToManyField]`, so the column attributes (`column`, `db_type()`, `primary_key`) are readable off a field without an `isinstance` check first. `add_field()` enforces that by raising `TypeError` for a field that is neither a `ColumnField` nor a `ManyToManyField` ([996c099e2b](https://github.com/dropseed/plain/commit/996c099e2b), [9f0d0da22d](https://github.com/dropseed/plain/commit/9f0d0da22d))
|
|
35
|
+
- The `postgres.non_local_field_reference` preflight check is removed. It rejected an `indexes` or `constraints` entry naming a field "not local to" the model — unreachable without inheritance, since a forward field found on the model is always the model's own ([9f0d0da22d](https://github.com/dropseed/plain/commit/9f0d0da22d))
|
|
36
|
+
- `Field.name` is typed `str` instead of `str | None` — it starts as `""` and becomes the real name in `set_attributes_from_name()`. That drops roughly twenty `assert self.name is not None` guards from the field descriptor, save, and preflight paths. `Field.deconstruct()` (and the `ColumnField`, `DefaultableField`, `ChoicesField`, `RelatedField`, `ForeignKeyField`, and `ManyToManyField` overrides) returns `tuple[str, str, list, dict]` accordingly ([51cb71f758](https://github.com/dropseed/plain/commit/51cb71f758))
|
|
37
|
+
- `get_joining_columns()` moved from `ForeignObjectRel` to `ForeignKeyRel`. It delegates to `field.get_joining_columns(reverse_join=True)`, which only a foreign key has ([51cb71f758](https://github.com/dropseed/plain/commit/51cb71f758))
|
|
38
|
+
- `PathInfo.join_field` is typed `ForeignKeyField | ForeignKeyRel` (was `ForeignKeyField | ForeignObjectRel`), and `select_related_descend()` is a `TypeGuard[RelatedField]` so a passing field narrows at the call site ([51cb71f758](https://github.com/dropseed/plain/commit/51cb71f758))
|
|
39
|
+
- `QuerySet._update()`, `_insert()`, `_batched_insert()`, `UpdateQuery.add_update_fields()`, and `InsertQuery.insert_values()` accept a `Sequence` of fields rather than requiring a `list`, so an `ImmutableList` from `Meta` can be passed straight through ([51cb71f758](https://github.com/dropseed/plain/commit/51cb71f758))
|
|
40
|
+
|
|
41
|
+
### Upgrade instructions
|
|
42
|
+
|
|
43
|
+
- Replace `_model_meta.local_fields`, `_model_meta.concrete_fields`, and `_model_meta.local_concrete_fields` with `_model_meta.fields`. In Plain these were always the same set of fields.
|
|
44
|
+
- Replace `_model_meta.local_many_to_many` with `_model_meta.many_to_many`.
|
|
45
|
+
- Replace `_model_meta._non_pk_concrete_field_names` with `_model_meta._non_pk_field_names`.
|
|
46
|
+
- If you have a custom `Field` subclass, `deconstruct()` now returns `str` as its first element rather than `str | None`, and `self.name` is `""` (not `None`) before the field is contributed to a model class. Update any annotation or `is None` check accordingly.
|
|
47
|
+
- If you called `get_joining_columns()` on a reverse relation, it is now available on `ForeignKeyRel` only.
|
|
48
|
+
|
|
3
49
|
## [0.115.0](https://github.com/dropseed/plain/releases/plain-postgres@0.115.0) (2026-09-02)
|
|
4
50
|
|
|
5
51
|
### What's changed
|
|
@@ -27,7 +27,6 @@ from plain.postgres.exceptions import (
|
|
|
27
27
|
)
|
|
28
28
|
from plain.postgres.expressions import RawSQL, Value
|
|
29
29
|
from plain.postgres.fields import DATABASE_DEFAULT, Field
|
|
30
|
-
from plain.postgres.fields.base import ColumnField
|
|
31
30
|
from plain.postgres.fields.related import RelatedField
|
|
32
31
|
from plain.postgres.fields.reverse_related import ForeignObjectRel
|
|
33
32
|
from plain.postgres.meta import Meta
|
|
@@ -123,14 +122,7 @@ class Model(metaclass=ModelBase):
|
|
|
123
122
|
for field in meta.fields:
|
|
124
123
|
from plain.postgres.fields.related import RelatedField
|
|
125
124
|
|
|
126
|
-
# meta.fields excludes ManyToManyField, so every iterated field
|
|
127
|
-
# is column-backed and exposes the ColumnField surface.
|
|
128
|
-
assert isinstance(field, ColumnField)
|
|
129
|
-
|
|
130
125
|
is_related_object = False
|
|
131
|
-
# Virtual field
|
|
132
|
-
if field.name not in kwargs and field.column is None:
|
|
133
|
-
continue
|
|
134
126
|
if isinstance(field, RelatedField) and isinstance(
|
|
135
127
|
field.remote_field, ForeignObjectRel
|
|
136
128
|
):
|
|
@@ -193,16 +185,14 @@ class Model(metaclass=ModelBase):
|
|
|
193
185
|
|
|
194
186
|
@classmethod
|
|
195
187
|
def from_db(cls, field_names: Iterable[str], values: Sequence[Any]) -> Model:
|
|
196
|
-
if len(values) != len(cls._model_meta.
|
|
188
|
+
if len(values) != len(cls._model_meta.fields):
|
|
197
189
|
values_iter = iter(values)
|
|
198
190
|
values = [
|
|
199
191
|
next(values_iter) if f.name in field_names else DEFERRED
|
|
200
|
-
for f in cls._model_meta.
|
|
192
|
+
for f in cls._model_meta.fields
|
|
201
193
|
]
|
|
202
194
|
# Build kwargs dict from field names and values
|
|
203
|
-
field_dict = dict(
|
|
204
|
-
zip((f.name for f in cls._model_meta.concrete_fields), values)
|
|
205
|
-
)
|
|
195
|
+
field_dict = dict(zip((f.name for f in cls._model_meta.fields), values))
|
|
206
196
|
new = cls(_from_db=True, **field_dict)
|
|
207
197
|
new._state.adding = False
|
|
208
198
|
return new
|
|
@@ -279,11 +269,7 @@ class Model(metaclass=ModelBase):
|
|
|
279
269
|
"""
|
|
280
270
|
Return a set containing names of deferred fields for this instance.
|
|
281
271
|
"""
|
|
282
|
-
return {
|
|
283
|
-
f.name
|
|
284
|
-
for f in self._model_meta.concrete_fields
|
|
285
|
-
if f.name not in self.__dict__
|
|
286
|
-
}
|
|
272
|
+
return {f.name for f in self._model_meta.fields if f.name not in self.__dict__}
|
|
287
273
|
|
|
288
274
|
def refresh_from_db(self, fields: list[str] | None = None) -> None:
|
|
289
275
|
"""
|
|
@@ -320,15 +306,13 @@ class Model(metaclass=ModelBase):
|
|
|
320
306
|
db_instance_qs = db_instance_qs.only(*fields)
|
|
321
307
|
elif deferred_fields:
|
|
322
308
|
fields = [
|
|
323
|
-
f.name
|
|
324
|
-
for f in self._model_meta.concrete_fields
|
|
325
|
-
if f.name not in deferred_fields
|
|
309
|
+
f.name for f in self._model_meta.fields if f.name not in deferred_fields
|
|
326
310
|
]
|
|
327
311
|
db_instance_qs = db_instance_qs.only(*fields)
|
|
328
312
|
|
|
329
313
|
db_instance = db_instance_qs.get()
|
|
330
314
|
non_loaded_fields = db_instance.get_deferred_fields()
|
|
331
|
-
for field in self._model_meta.
|
|
315
|
+
for field in self._model_meta.fields:
|
|
332
316
|
if field.name in non_loaded_fields:
|
|
333
317
|
# This field wasn't refreshed - skip ahead.
|
|
334
318
|
continue
|
|
@@ -404,12 +388,12 @@ class Model(metaclass=ModelBase):
|
|
|
404
388
|
if not fields:
|
|
405
389
|
return self # explicit "update nothing" -- no-op
|
|
406
390
|
fields = frozenset(fields)
|
|
407
|
-
field_names = self._model_meta.
|
|
391
|
+
field_names = self._model_meta._non_pk_field_names
|
|
408
392
|
non_model_fields = fields.difference(field_names)
|
|
409
393
|
if non_model_fields:
|
|
410
394
|
raise ValueError(
|
|
411
|
-
"The following fields do not exist in this model
|
|
412
|
-
"fields
|
|
395
|
+
"The following fields do not exist in this model or are m2m "
|
|
396
|
+
"fields: "
|
|
413
397
|
f"{', '.join(sorted(non_model_fields))}"
|
|
414
398
|
)
|
|
415
399
|
deferred_in_update = fields & deferred_fields
|
|
@@ -420,7 +404,7 @@ class Model(metaclass=ModelBase):
|
|
|
420
404
|
)
|
|
421
405
|
elif deferred_fields:
|
|
422
406
|
# Loaded via .only()/.defer() -- write just the loaded fields.
|
|
423
|
-
loaded = self._model_meta.
|
|
407
|
+
loaded = self._model_meta._non_pk_field_names - deferred_fields
|
|
424
408
|
if loaded:
|
|
425
409
|
fields = loaded
|
|
426
410
|
self._prepare_related_fields_for_save(
|
|
@@ -484,7 +468,7 @@ class Model(metaclass=ModelBase):
|
|
|
484
468
|
fields from RETURNING. Omits id from the INSERT when unset so Postgres
|
|
485
469
|
generates the identity value."""
|
|
486
470
|
meta = self._model_meta
|
|
487
|
-
fields = list(meta.
|
|
471
|
+
fields = list(meta.fields)
|
|
488
472
|
if self.id is None:
|
|
489
473
|
id_field = meta.get_forward_field("id")
|
|
490
474
|
fields = [f for f in fields if f is not id_field]
|
|
@@ -494,7 +478,6 @@ class Model(metaclass=ModelBase):
|
|
|
494
478
|
)
|
|
495
479
|
if results:
|
|
496
480
|
for value, field in zip(results[0], returning_fields):
|
|
497
|
-
assert field.name is not None
|
|
498
481
|
setattr(self, field.name, value)
|
|
499
482
|
|
|
500
483
|
def _update_row(self, fields: Iterable[str] | None) -> None:
|
|
@@ -502,7 +485,7 @@ class Model(metaclass=ModelBase):
|
|
|
502
485
|
row matched -- update() targets an existing row and has no INSERT
|
|
503
486
|
fallback (that's create())."""
|
|
504
487
|
meta = self._model_meta
|
|
505
|
-
non_pks = [f for f in meta.
|
|
488
|
+
non_pks = [f for f in meta.fields if not f.primary_key]
|
|
506
489
|
if fields:
|
|
507
490
|
non_pks = [f for f in non_pks if f.name in fields]
|
|
508
491
|
values = [(f, f.pre_save(self, False)) for f in non_pks]
|
|
@@ -525,7 +508,7 @@ class Model(metaclass=ModelBase):
|
|
|
525
508
|
) -> None:
|
|
526
509
|
# Ensure that a model instance without a PK hasn't been assigned to
|
|
527
510
|
# a ForeignKeyField on this model. If the field is nullable, allowing the save would result in silent data loss.
|
|
528
|
-
for field in self._model_meta.
|
|
511
|
+
for field in self._model_meta.fields:
|
|
529
512
|
if field_names and field.name not in field_names:
|
|
530
513
|
continue
|
|
531
514
|
# If the related field isn't cached, then an instance hasn't been
|
|
@@ -578,7 +561,6 @@ class Model(metaclass=ModelBase):
|
|
|
578
561
|
with transaction.mark_for_rollback_on_error():
|
|
579
562
|
count = self._model_meta.base_queryset.filter(id=self.id)._raw_delete()
|
|
580
563
|
id_field = self._model_meta.get_forward_field("id")
|
|
581
|
-
assert id_field.name is not None
|
|
582
564
|
setattr(self, id_field.name, None)
|
|
583
565
|
# Only the id is cleared -- every other field value survives so callers
|
|
584
566
|
# can still reference a deleted row (correlate it, log it, check it's
|
|
@@ -611,7 +593,7 @@ class Model(metaclass=ModelBase):
|
|
|
611
593
|
meta = meta or self._model_meta
|
|
612
594
|
return {
|
|
613
595
|
field.name: Value(field.value_from_object(self), field)
|
|
614
|
-
for field in meta.
|
|
596
|
+
for field in meta.fields
|
|
615
597
|
if field.name not in exclude
|
|
616
598
|
}
|
|
617
599
|
|
|
@@ -755,9 +737,9 @@ class Model(metaclass=ModelBase):
|
|
|
755
737
|
def _check_fields(cls) -> list[PreflightResult]:
|
|
756
738
|
"""Perform all field checks."""
|
|
757
739
|
errors: list[PreflightResult] = []
|
|
758
|
-
for field in cls._model_meta.
|
|
740
|
+
for field in cls._model_meta.fields:
|
|
759
741
|
errors.extend(field.preflight(from_model=cls))
|
|
760
|
-
for field in cls._model_meta.
|
|
742
|
+
for field in cls._model_meta.many_to_many:
|
|
761
743
|
errors.extend(field.preflight(from_model=cls))
|
|
762
744
|
return errors
|
|
763
745
|
|
|
@@ -768,13 +750,16 @@ class Model(metaclass=ModelBase):
|
|
|
768
750
|
errors: list[PreflightResult] = []
|
|
769
751
|
seen_intermediary_signatures = []
|
|
770
752
|
|
|
771
|
-
fields = cls._model_meta.
|
|
753
|
+
fields = cls._model_meta.many_to_many
|
|
772
754
|
|
|
773
|
-
# Skip when the target model wasn't found
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
755
|
+
# Skip when the target or relationship model wasn't found; the field's
|
|
756
|
+
# own preflight reports those.
|
|
757
|
+
fields = (
|
|
758
|
+
f
|
|
759
|
+
for f in fields
|
|
760
|
+
if not isinstance(f.remote_field.model_ref, str)
|
|
761
|
+
and not isinstance(f.remote_field.through_ref, str)
|
|
762
|
+
)
|
|
778
763
|
|
|
779
764
|
for f in fields:
|
|
780
765
|
signature = (
|
|
@@ -800,9 +785,7 @@ class Model(metaclass=ModelBase):
|
|
|
800
785
|
def _check_id_field(cls) -> list[PreflightResult]:
|
|
801
786
|
"""Disallow user-defined fields named ``id``."""
|
|
802
787
|
if any(
|
|
803
|
-
f
|
|
804
|
-
for f in cls._model_meta.local_fields
|
|
805
|
-
if f.name == "id" and not f.auto_created
|
|
788
|
+
f for f in cls._model_meta.fields if f.name == "id" and not f.auto_created
|
|
806
789
|
):
|
|
807
790
|
return [
|
|
808
791
|
PreflightResult(
|
|
@@ -819,7 +802,7 @@ class Model(metaclass=ModelBase):
|
|
|
819
802
|
errors: list[PreflightResult] = []
|
|
820
803
|
used_fields = {} # name -> field
|
|
821
804
|
|
|
822
|
-
for f in cls._model_meta.
|
|
805
|
+
for f in cls._model_meta.fields:
|
|
823
806
|
clash = used_fields.get(f.name)
|
|
824
807
|
# Note that we may detect clash between user-defined non-unique
|
|
825
808
|
# field "id" and automatically added unique field "id", both
|
|
@@ -847,7 +830,7 @@ class Model(metaclass=ModelBase):
|
|
|
847
830
|
used_column_names: list[str] = []
|
|
848
831
|
errors: list[PreflightResult] = []
|
|
849
832
|
|
|
850
|
-
for f in cls._model_meta.
|
|
833
|
+
for f in cls._model_meta.fields:
|
|
851
834
|
column_name = f.column
|
|
852
835
|
|
|
853
836
|
# Ensure the column name is not already in use.
|
|
@@ -915,7 +898,7 @@ class Model(metaclass=ModelBase):
|
|
|
915
898
|
@classmethod
|
|
916
899
|
def _check_single_primary_key(cls) -> list[PreflightResult]:
|
|
917
900
|
errors: list[PreflightResult] = []
|
|
918
|
-
if sum(1 for f in cls._model_meta.
|
|
901
|
+
if sum(1 for f in cls._model_meta.fields if f.primary_key) > 1:
|
|
919
902
|
errors.append(
|
|
920
903
|
PreflightResult(
|
|
921
904
|
fix="The model cannot have more than one field with "
|
|
@@ -966,11 +949,11 @@ class Model(metaclass=ModelBase):
|
|
|
966
949
|
include for index in cls.model_options.indexes for include in index.include
|
|
967
950
|
]
|
|
968
951
|
fields += references
|
|
969
|
-
errors.extend(cls.
|
|
952
|
+
errors.extend(cls._check_referenced_fields(fields, "indexes"))
|
|
970
953
|
return errors
|
|
971
954
|
|
|
972
955
|
@classmethod
|
|
973
|
-
def
|
|
956
|
+
def _check_referenced_fields(
|
|
974
957
|
cls, fields: Iterable[str], option: str
|
|
975
958
|
) -> list[PreflightResult]:
|
|
976
959
|
# In order to avoid hitting the relation tree prematurely, we use our
|
|
@@ -1003,15 +986,6 @@ class Model(metaclass=ModelBase):
|
|
|
1003
986
|
id="postgres.m2m_field_in_meta_option",
|
|
1004
987
|
)
|
|
1005
988
|
)
|
|
1006
|
-
elif field not in cls._model_meta.local_fields:
|
|
1007
|
-
errors.append(
|
|
1008
|
-
PreflightResult(
|
|
1009
|
-
fix=f"'{option}' refers to field '{field_name}' which is not local to model "
|
|
1010
|
-
f"'{cls.model_options.object_name}'.",
|
|
1011
|
-
obj=cls,
|
|
1012
|
-
id="postgres.non_local_field_reference",
|
|
1013
|
-
)
|
|
1014
|
-
)
|
|
1015
989
|
return errors
|
|
1016
990
|
|
|
1017
991
|
@classmethod
|
|
@@ -1059,7 +1033,11 @@ class Model(metaclass=ModelBase):
|
|
|
1059
1033
|
fld = None
|
|
1060
1034
|
for part in field.split(LOOKUP_SEP):
|
|
1061
1035
|
try:
|
|
1062
|
-
|
|
1036
|
+
if _cls is None:
|
|
1037
|
+
# The previous part was not a relation, so there is
|
|
1038
|
+
# no model left to look the next part up on.
|
|
1039
|
+
raise FieldDoesNotExist(part)
|
|
1040
|
+
fld = _cls._model_meta.get_field(part)
|
|
1063
1041
|
if isinstance(fld, RelatedField):
|
|
1064
1042
|
_cls = fld.path_infos[-1].to_meta.model
|
|
1065
1043
|
else:
|
|
@@ -1089,9 +1067,9 @@ class Model(metaclass=ModelBase):
|
|
|
1089
1067
|
meta = cls._model_meta
|
|
1090
1068
|
valid_fields = set(
|
|
1091
1069
|
chain.from_iterable(
|
|
1092
|
-
(f.
|
|
1093
|
-
if
|
|
1094
|
-
else (f.
|
|
1070
|
+
(f.field.related_query_name(),)
|
|
1071
|
+
if isinstance(f, ForeignObjectRel)
|
|
1072
|
+
else (f.name,)
|
|
1095
1073
|
for f in chain(meta.fields, meta.related_objects)
|
|
1096
1074
|
)
|
|
1097
1075
|
)
|
|
@@ -1121,7 +1099,7 @@ class Model(metaclass=ModelBase):
|
|
|
1121
1099
|
# silently truncate, so we check for names that are too long
|
|
1122
1100
|
allowed_len = MAX_NAME_LENGTH
|
|
1123
1101
|
|
|
1124
|
-
for f in cls._model_meta.
|
|
1102
|
+
for f in cls._model_meta.fields:
|
|
1125
1103
|
column_name = f.column
|
|
1126
1104
|
|
|
1127
1105
|
# Check if column name is too long for the database.
|
|
@@ -1135,13 +1113,13 @@ class Model(metaclass=ModelBase):
|
|
|
1135
1113
|
)
|
|
1136
1114
|
)
|
|
1137
1115
|
|
|
1138
|
-
for f in cls._model_meta.
|
|
1116
|
+
for f in cls._model_meta.many_to_many:
|
|
1139
1117
|
# Skip nonexistent models.
|
|
1140
|
-
if isinstance(f.remote_field.
|
|
1118
|
+
if isinstance(f.remote_field.through_ref, str):
|
|
1141
1119
|
continue
|
|
1142
1120
|
|
|
1143
1121
|
# Check if column name for the M2M field is too long for the database.
|
|
1144
|
-
for m2m in f.remote_field.
|
|
1122
|
+
for m2m in f.remote_field.through_ref._model_meta.fields:
|
|
1145
1123
|
rel_name = m2m.column
|
|
1146
1124
|
if rel_name is not None and len(rel_name) > allowed_len:
|
|
1147
1125
|
errors.append(
|
|
@@ -1235,7 +1213,7 @@ class Model(metaclass=ModelBase):
|
|
|
1235
1213
|
id="postgres.constraint_refers_to_joined_field",
|
|
1236
1214
|
)
|
|
1237
1215
|
)
|
|
1238
|
-
errors.extend(cls.
|
|
1216
|
+
errors.extend(cls._check_referenced_fields(fields, "constraints"))
|
|
1239
1217
|
return errors
|
|
1240
1218
|
|
|
1241
1219
|
|
|
@@ -535,9 +535,7 @@ class DatabaseConnection:
|
|
|
535
535
|
tables = set()
|
|
536
536
|
for model in get_migratable_models():
|
|
537
537
|
tables.add(model.model_options.db_table)
|
|
538
|
-
tables.update(
|
|
539
|
-
f.m2m_db_table() for f in model._model_meta.local_many_to_many
|
|
540
|
-
)
|
|
538
|
+
tables.update(f.m2m_db_table() for f in model._model_meta.many_to_many)
|
|
541
539
|
tables = list(tables)
|
|
542
540
|
if only_existing:
|
|
543
541
|
existing_tables = set(self.table_names(include_views=include_views))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from types import NoneType
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
6
|
from plain.exceptions import ValidationError
|
|
7
7
|
from plain.postgres.constants import LOOKUP_SEP
|
|
@@ -454,7 +454,7 @@ class UniqueConstraint(BaseConstraint):
|
|
|
454
454
|
field_names[-1] = f"and {field_names[-1]}"
|
|
455
455
|
# Comma-join when more than two, otherwise just space-join.
|
|
456
456
|
sep = ", " if len(field_names) > 2 else " "
|
|
457
|
-
params["field_label"] = sep.join(
|
|
457
|
+
params["field_label"] = sep.join(field_names)
|
|
458
458
|
|
|
459
459
|
# Use the first field's message format.
|
|
460
460
|
message = meta.get_forward_field(unique_check[0]).unique_error_message
|
|
@@ -21,7 +21,6 @@ from ..ddl import (
|
|
|
21
21
|
compile_literal_default_sql,
|
|
22
22
|
)
|
|
23
23
|
from ..dialect import quote_name
|
|
24
|
-
from ..fields.base import ColumnField
|
|
25
24
|
from ..fields.related import ForeignKeyField
|
|
26
25
|
from ..indexes import Index
|
|
27
26
|
from ..introspection import (
|
|
@@ -586,9 +585,7 @@ def _compare_columns(
|
|
|
586
585
|
statuses: list[ColumnStatus] = []
|
|
587
586
|
expected_col_names: set[str] = set()
|
|
588
587
|
|
|
589
|
-
for f in model._model_meta.
|
|
590
|
-
if not isinstance(f, ColumnField):
|
|
591
|
-
continue
|
|
588
|
+
for f in model._model_meta.fields:
|
|
592
589
|
db_type = f.db_type()
|
|
593
590
|
if db_type is None:
|
|
594
591
|
continue
|
|
@@ -635,8 +632,6 @@ def _compare_columns(
|
|
|
635
632
|
if f.primary_key:
|
|
636
633
|
pk_suffix = f.db_type_suffix() or ""
|
|
637
634
|
|
|
638
|
-
# Fields reached via local_fields are always contributed (name set).
|
|
639
|
-
assert f.name is not None
|
|
640
635
|
statuses.append(
|
|
641
636
|
ColumnStatus(
|
|
642
637
|
name=f.column,
|
|
@@ -1193,7 +1188,7 @@ def _compare_check_constraints(
|
|
|
1193
1188
|
# ignored rather than surfaced as undeclared user constraints.
|
|
1194
1189
|
internal_checks = {
|
|
1195
1190
|
generate_notnull_check_name(table, f.column)
|
|
1196
|
-
for f in model._model_meta.
|
|
1191
|
+
for f in model._model_meta.fields
|
|
1197
1192
|
if f.db_type() is not None
|
|
1198
1193
|
}
|
|
1199
1194
|
|
|
@@ -1245,7 +1240,7 @@ def _compare_foreign_keys(
|
|
|
1245
1240
|
# Expected FKs from model fields, keyed by shape (column, target_table,
|
|
1246
1241
|
# target_column) — the DB-side identity, since names can lag a rename.
|
|
1247
1242
|
expected_fks: dict[tuple[str, str, str], ForeignKeyField] = {}
|
|
1248
|
-
for f in model._model_meta.
|
|
1243
|
+
for f in model._model_meta.fields:
|
|
1249
1244
|
if isinstance(f, ForeignKeyField):
|
|
1250
1245
|
to_table = f.target_field.model.model_options.db_table
|
|
1251
1246
|
expected_fks[(f.column, to_table, f.target_field.column)] = f
|
|
@@ -1698,11 +1698,23 @@ class OrderBy(Expression):
|
|
|
1698
1698
|
self.nulls_last = None
|
|
1699
1699
|
return self
|
|
1700
1700
|
|
|
1701
|
-
def asc(self) ->
|
|
1701
|
+
def asc(self, **kwargs: Any) -> OrderBy:
|
|
1702
|
+
if kwargs:
|
|
1703
|
+
raise TypeError(
|
|
1704
|
+
"OrderBy.asc() takes no options; set nulls_first/nulls_last on "
|
|
1705
|
+
"the OrderBy itself."
|
|
1706
|
+
)
|
|
1702
1707
|
self.descending = False
|
|
1708
|
+
return self
|
|
1703
1709
|
|
|
1704
|
-
def desc(self) ->
|
|
1710
|
+
def desc(self, **kwargs: Any) -> OrderBy:
|
|
1711
|
+
if kwargs:
|
|
1712
|
+
raise TypeError(
|
|
1713
|
+
"OrderBy.desc() takes no options; set nulls_first/nulls_last on "
|
|
1714
|
+
"the OrderBy itself."
|
|
1715
|
+
)
|
|
1705
1716
|
self.descending = True
|
|
1717
|
+
return self
|
|
1706
1718
|
|
|
1707
1719
|
|
|
1708
1720
|
class Window(Expression):
|
|
@@ -110,11 +110,11 @@ class Field[T](RegisterLookupMixin):
|
|
|
110
110
|
cast_db_type_sql: str | None = None
|
|
111
111
|
|
|
112
112
|
# Instance attributes set during field lifecycle
|
|
113
|
-
# Set by __init__
|
|
114
|
-
|
|
113
|
+
# Set by __init__; becomes the real field name once contributed to a
|
|
114
|
+
# model class (set_attributes_from_name, called by contribute_to_class)
|
|
115
|
+
name: str
|
|
115
116
|
# Set by set_attributes_from_name (called by contribute_to_class)
|
|
116
117
|
column: str
|
|
117
|
-
concrete: bool
|
|
118
118
|
# Set by contribute_to_class
|
|
119
119
|
model: type[Model]
|
|
120
120
|
|
|
@@ -134,7 +134,7 @@ class Field[T](RegisterLookupMixin):
|
|
|
134
134
|
non_migration_attrs: tuple[str, ...] = ()
|
|
135
135
|
|
|
136
136
|
def __init__(self) -> None:
|
|
137
|
-
self.name =
|
|
137
|
+
self.name = "" # Set by set_attributes_from_name
|
|
138
138
|
self.primary_key = False
|
|
139
139
|
self.auto_created = False
|
|
140
140
|
|
|
@@ -151,8 +151,8 @@ class Field[T](RegisterLookupMixin):
|
|
|
151
151
|
def __repr__(self) -> str:
|
|
152
152
|
"""Display the module, class, and name of the field."""
|
|
153
153
|
path = f"{self.__class__.__module__}.{self.__class__.__qualname__}"
|
|
154
|
-
name = getattr(self, "name",
|
|
155
|
-
if name
|
|
154
|
+
name = getattr(self, "name", "")
|
|
155
|
+
if name:
|
|
156
156
|
return f"<{path}: {name}>"
|
|
157
157
|
return f"<{path}>"
|
|
158
158
|
|
|
@@ -164,7 +164,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
164
164
|
Check if field name is valid, i.e. 1) does not end with an
|
|
165
165
|
underscore, 2) does not contain "__" and 3) is not "id".
|
|
166
166
|
"""
|
|
167
|
-
assert self.name is not None, "Field name must be set before checking"
|
|
168
167
|
if self.name.endswith("_"):
|
|
169
168
|
return [
|
|
170
169
|
PreflightResult(
|
|
@@ -215,7 +214,7 @@ class Field[T](RegisterLookupMixin):
|
|
|
215
214
|
"""
|
|
216
215
|
return sql, params
|
|
217
216
|
|
|
218
|
-
def deconstruct(self) -> tuple[str
|
|
217
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
|
219
218
|
"""
|
|
220
219
|
Return enough information to recreate the field as a 4-tuple:
|
|
221
220
|
|
|
@@ -259,7 +258,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
259
258
|
cls_name = self.__class__.__qualname__
|
|
260
259
|
if getattr(_postgres_root, cls_name, None) is self.__class__:
|
|
261
260
|
path = f"plain.postgres.{cls_name}"
|
|
262
|
-
# Note: self.name can be None during migration state rendering when fields are cloned
|
|
263
261
|
return (self.name, path, [], keywords)
|
|
264
262
|
|
|
265
263
|
def clone(self) -> Self:
|
|
@@ -305,7 +303,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
305
303
|
# values - so, this is very close to normal pickle.
|
|
306
304
|
state = self.__dict__.copy()
|
|
307
305
|
return _empty, (self.__class__,), state
|
|
308
|
-
assert self.name is not None
|
|
309
306
|
options = model.model_options
|
|
310
307
|
return _load_field, (
|
|
311
308
|
options.package_label,
|
|
@@ -378,7 +375,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
378
375
|
# The database column is the field name. A foreign key overrides this
|
|
379
376
|
# method only to append the "_id" suffix to `column`.
|
|
380
377
|
self.column = self.name
|
|
381
|
-
self.concrete = self.column is not None
|
|
382
378
|
|
|
383
379
|
def contribute_to_class(self, cls: type[Model], name: str) -> None:
|
|
384
380
|
"""
|
|
@@ -394,7 +390,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
394
390
|
# Field is its own descriptor; make sure it is set on the class so
|
|
395
391
|
# attribute access hits __get__/__set__.
|
|
396
392
|
if self.column:
|
|
397
|
-
assert self.name is not None
|
|
398
393
|
setattr(cls, self.name, self)
|
|
399
394
|
|
|
400
395
|
# Descriptor protocol implementation
|
|
@@ -422,7 +417,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
422
417
|
return self
|
|
423
418
|
|
|
424
419
|
# Instance access - get value from instance dict
|
|
425
|
-
assert self.name is not None
|
|
426
420
|
data = instance.__dict__
|
|
427
421
|
field_name = self.name
|
|
428
422
|
|
|
@@ -432,9 +426,7 @@ class Field[T](RegisterLookupMixin):
|
|
|
432
426
|
# instance cheap to use beyond its primary key.
|
|
433
427
|
if field_name not in data:
|
|
434
428
|
missing = [
|
|
435
|
-
f.name
|
|
436
|
-
for f in instance._model_meta.concrete_fields
|
|
437
|
-
if f.name not in data
|
|
429
|
+
f.name for f in instance._model_meta.fields if f.name not in data
|
|
438
430
|
]
|
|
439
431
|
instance.refresh_from_db(fields=missing)
|
|
440
432
|
|
|
@@ -472,7 +464,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
472
464
|
stored = self.to_python(value)
|
|
473
465
|
|
|
474
466
|
# Store in instance dict
|
|
475
|
-
assert self.name is not None
|
|
476
467
|
instance.__dict__[self.name] = stored
|
|
477
468
|
|
|
478
469
|
def __delete__(self, instance: Model) -> None:
|
|
@@ -481,7 +472,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
481
472
|
|
|
482
473
|
Removes the value from instance.__dict__.
|
|
483
474
|
"""
|
|
484
|
-
assert self.name is not None
|
|
485
475
|
try:
|
|
486
476
|
del instance.__dict__[self.name]
|
|
487
477
|
except KeyError:
|
|
@@ -491,7 +481,6 @@ class Field[T](RegisterLookupMixin):
|
|
|
491
481
|
|
|
492
482
|
def pre_save(self, model_instance: Model, add: bool) -> T | None:
|
|
493
483
|
"""Return field's value just before saving."""
|
|
494
|
-
assert self.name is not None
|
|
495
484
|
return getattr(model_instance, self.name)
|
|
496
485
|
|
|
497
486
|
def get_prep_value(self, value: Any) -> Any:
|
|
@@ -544,12 +533,10 @@ class Field[T](RegisterLookupMixin):
|
|
|
544
533
|
return self.get_db_default_expression() is not None
|
|
545
534
|
|
|
546
535
|
def save_form_data(self, instance: Model, data: Any) -> None:
|
|
547
|
-
assert self.name is not None
|
|
548
536
|
setattr(instance, self.name, data)
|
|
549
537
|
|
|
550
538
|
def value_from_object(self, obj: Model) -> T | None:
|
|
551
539
|
"""Return the value of this field in the given model instance."""
|
|
552
|
-
assert self.name is not None
|
|
553
540
|
return getattr(obj, self.name)
|
|
554
541
|
|
|
555
542
|
|
|
@@ -653,7 +640,7 @@ class ColumnField[T](Field[T]):
|
|
|
653
640
|
return None
|
|
654
641
|
return self._default_empty_value
|
|
655
642
|
|
|
656
|
-
def deconstruct(self) -> tuple[str
|
|
643
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
|
657
644
|
name, path, args, kwargs = super().deconstruct()
|
|
658
645
|
if self.required is not True:
|
|
659
646
|
kwargs["required"] = self.required
|
|
@@ -750,7 +737,7 @@ class DefaultableField[T](ColumnField[T]):
|
|
|
750
737
|
# shared state across instances.
|
|
751
738
|
return copy.deepcopy(self.default)
|
|
752
739
|
|
|
753
|
-
def deconstruct(self) -> tuple[str
|
|
740
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
|
754
741
|
name, path, args, kwargs = super().deconstruct()
|
|
755
742
|
if self.default is not NOT_PROVIDED:
|
|
756
743
|
kwargs["default"] = self.default
|
|
@@ -891,7 +878,7 @@ class ChoicesField[T](DefaultableField[T]):
|
|
|
891
878
|
)
|
|
892
879
|
super().validate(value, model_instance)
|
|
893
880
|
|
|
894
|
-
def deconstruct(self) -> tuple[str
|
|
881
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
|
895
882
|
name, path, args, kwargs = super().deconstruct()
|
|
896
883
|
if self.choices is not None:
|
|
897
884
|
choices = self.choices
|
|
@@ -41,7 +41,7 @@ class BinaryField[
|
|
|
41
41
|
if self.max_length is not None:
|
|
42
42
|
self.validators.append(MaxLengthValidator(self.max_length))
|
|
43
43
|
|
|
44
|
-
def deconstruct(self) -> tuple[str
|
|
44
|
+
def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
|
|
45
45
|
name, path, args, kwargs = super().deconstruct()
|
|
46
46
|
if self.max_length is not None:
|
|
47
47
|
kwargs["max_length"] = self.max_length
|
|
@@ -14,7 +14,7 @@ except ImportError:
|
|
|
14
14
|
InvalidToken = None # ty: ignore[invalid-assignment]
|
|
15
15
|
MultiFernet = None # ty: ignore[invalid-assignment]
|
|
16
16
|
hashes = None # ty: ignore[invalid-assignment]
|
|
17
|
-
PBKDF2HMAC = None
|
|
17
|
+
PBKDF2HMAC = None
|
|
18
18
|
|
|
19
19
|
from plain.postgres.lookups import Exact, IsNull
|
|
20
20
|
from plain.runtime import settings
|