plain.postgres 0.106.0__tar.gz → 0.107.1__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.
Files changed (216) hide show
  1. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/PKG-INFO +3 -3
  2. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/CHANGELOG.md +28 -0
  3. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/README.md +2 -2
  4. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/constraints.py +1 -1
  5. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/forms.py +28 -21
  6. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/meta.py +3 -2
  7. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/executor.py +1 -1
  8. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/state.py +3 -1
  9. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/query.py +1 -0
  10. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/compiler.py +2 -2
  11. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/query.py +3 -1
  12. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/pyproject.toml +1 -1
  13. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/urls.py +5 -0
  14. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/views.py +8 -1
  15. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_modelform_roundtrip.py +28 -0
  16. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/.gitignore +0 -0
  17. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/CLAUDE.md +0 -0
  18. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/LICENSE +0 -0
  19. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/README.md +0 -0
  20. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/__init__.py +0 -0
  21. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/adapters.py +0 -0
  22. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
  23. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
  24. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/aggregates.py +0 -0
  25. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/base.py +0 -0
  26. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/__init__.py +0 -0
  27. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/converge.py +0 -0
  28. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/core.py +0 -0
  29. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/decorators.py +0 -0
  30. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/diagnose.py +0 -0
  31. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/migrations.py +0 -0
  32. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/schema.py +0 -0
  33. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/cli/sync.py +0 -0
  34. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/config.py +0 -0
  35. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/connection.py +0 -0
  36. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/constants.py +0 -0
  37. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/convergence/__init__.py +0 -0
  38. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/convergence/analysis.py +0 -0
  39. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/convergence/fixes.py +0 -0
  40. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/convergence/planning.py +0 -0
  41. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/database_url.py +0 -0
  42. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/db.py +0 -0
  43. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/ddl.py +0 -0
  44. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/default_settings.py +0 -0
  45. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/deletion.py +0 -0
  46. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/dialect.py +0 -0
  47. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/entrypoints.py +0 -0
  48. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/enums.py +0 -0
  49. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/exceptions.py +0 -0
  50. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/expressions.py +0 -0
  51. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/__init__.py +0 -0
  52. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/base.py +0 -0
  53. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/binary.py +0 -0
  54. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/boolean.py +0 -0
  55. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/duration.py +0 -0
  56. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/encrypted.py +0 -0
  57. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/json.py +0 -0
  58. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/mixins.py +0 -0
  59. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/network.py +0 -0
  60. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/numeric.py +0 -0
  61. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/primary_key.py +0 -0
  62. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/related.py +0 -0
  63. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/related_descriptors.py +0 -0
  64. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/related_lookups.py +0 -0
  65. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/related_managers.py +0 -0
  66. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/reverse_descriptors.py +0 -0
  67. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/reverse_related.py +0 -0
  68. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/temporal.py +0 -0
  69. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/text.py +0 -0
  70. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/timezones.py +0 -0
  71. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/fields/uuid.py +0 -0
  72. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/__init__.py +0 -0
  73. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/comparison.py +0 -0
  74. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/datetime.py +0 -0
  75. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/math.py +0 -0
  76. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/mixins.py +0 -0
  77. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/random.py +0 -0
  78. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/text.py +0 -0
  79. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/uuid.py +0 -0
  80. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/functions/window.py +0 -0
  81. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/indexes.py +0 -0
  82. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/__init__.py +0 -0
  83. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/__init__.py +0 -0
  84. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
  85. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
  86. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/checks_structural.py +0 -0
  87. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/context.py +0 -0
  88. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/helpers.py +0 -0
  89. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/ownership.py +0 -0
  90. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/runner.py +0 -0
  91. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/health/types.py +0 -0
  92. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/introspection/schema.py +0 -0
  93. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/lookups.py +0 -0
  94. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/middleware.py +0 -0
  95. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/__init__.py +0 -0
  96. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/autodetector.py +0 -0
  97. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/exceptions.py +0 -0
  98. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/graph.py +0 -0
  99. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/loader.py +0 -0
  100. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/migration.py +0 -0
  101. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/operations/__init__.py +0 -0
  102. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/operations/base.py +0 -0
  103. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/operations/fields.py +0 -0
  104. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/operations/models.py +0 -0
  105. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/operations/special.py +0 -0
  106. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/optimizer.py +0 -0
  107. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/questioner.py +0 -0
  108. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/recorder.py +0 -0
  109. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/serializer.py +0 -0
  110. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/utils.py +0 -0
  111. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/migrations/writer.py +0 -0
  112. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/options.py +0 -0
  113. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/otel.py +0 -0
  114. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/preflight.py +0 -0
  115. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/query_utils.py +0 -0
  116. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/registry.py +0 -0
  117. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/schema.py +0 -0
  118. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sources.py +0 -0
  119. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/__init__.py +0 -0
  120. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/constants.py +0 -0
  121. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/datastructures.py +0 -0
  122. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/sql/where.py +0 -0
  123. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/test/__init__.py +0 -0
  124. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/test/database.py +0 -0
  125. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/test/pytest.py +0 -0
  126. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/transaction.py +0 -0
  127. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/types.py +0 -0
  128. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/types.pyi +0 -0
  129. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/plain/postgres/utils.py +0 -0
  130. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/forms.py +0 -0
  131. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0001_initial.py +0 -0
  132. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  133. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  134. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
  135. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
  136. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0006_secretstore.py +0 -0
  137. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
  138. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
  139. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
  140. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
  141. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
  142. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
  143. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
  144. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0014_widget_rename_feature_tag_remove_carfeature_car_and_more.py +0 -0
  145. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
  146. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0016_formsexample.py +0 -0
  147. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
  148. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/0018_storageparametersexample.py +0 -0
  149. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/migrations/__init__.py +0 -0
  150. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/__init__.py +0 -0
  151. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/constraints.py +0 -0
  152. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/defaults.py +0 -0
  153. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/delete.py +0 -0
  154. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/encrypted.py +0 -0
  155. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/forms.py +0 -0
  156. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/indexes.py +0 -0
  157. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/iteration.py +0 -0
  158. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/mixins.py +0 -0
  159. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/nullability.py +0 -0
  160. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/querysets.py +0 -0
  161. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/relationships.py +0 -0
  162. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/storage_parameters.py +0 -0
  163. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/trees.py +0 -0
  164. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/examples/models/unregistered.py +0 -0
  165. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/settings.py +0 -0
  166. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/app/urls.py +0 -0
  167. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/conftest.py +0 -0
  168. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/conftest_convergence.py +0 -0
  169. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_autodetector_not_null_errors.py +0 -0
  170. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_autodetector_type_change.py +0 -0
  171. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_connection_isolation.py +0 -0
  172. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_connection_lifecycle.py +0 -0
  173. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_connection_pool.py +0 -0
  174. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_constraint_violation_error.py +0 -0
  175. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence.py +0 -0
  176. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_constraints.py +0 -0
  177. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_defaults.py +0 -0
  178. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_fk.py +0 -0
  179. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_indexes.py +0 -0
  180. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_nullability.py +0 -0
  181. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_storage_parameters.py +0 -0
  182. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_convergence_timeouts.py +0 -0
  183. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_db_expression_defaults.py +0 -0
  184. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_diagnose.py +0 -0
  185. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_executor_connection_hook.py +0 -0
  186. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_fk_characterization.py +0 -0
  187. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_health.py +0 -0
  188. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_introspection.py +0 -0
  189. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_literal_default_persistence.py +0 -0
  190. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_management_connection.py +0 -0
  191. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_migration_executor.py +0 -0
  192. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_no_callable_defaults.py +0 -0
  193. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_otel_metrics.py +0 -0
  194. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_preflight_duplicate_indexes.py +0 -0
  195. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_preflight_fk_coverage.py +0 -0
  196. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_schema_normalize_type.py +0 -0
  197. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/internal/test_schema_timeouts.py +0 -0
  198. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_create_update.py +0 -0
  199. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_database_url.py +0 -0
  200. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_deferred_loading.py +0 -0
  201. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_delete_behaviors.py +0 -0
  202. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_encrypted_fields.py +0 -0
  203. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_exceptions.py +0 -0
  204. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_field_defaults.py +0 -0
  205. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_functions_uuid.py +0 -0
  206. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_integrity_error_mapping.py +0 -0
  207. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_iterator.py +0 -0
  208. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_m2m.py +0 -0
  209. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_manager_assignment.py +0 -0
  210. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_manual_pk.py +0 -0
  211. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_mixins.py +0 -0
  212. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_queryset_repr.py +0 -0
  213. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_random_string_field.py +0 -0
  214. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_raw_query.py +0 -0
  215. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_read_only_transactions.py +0 -0
  216. {plain_postgres-0.106.0 → plain_postgres-0.107.1}/tests/public/test_related.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.postgres
3
- Version: 0.106.0
3
+ Version: 0.107.1
4
4
  Summary: Model your data and store it in a database.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -1066,7 +1066,7 @@ except ValidationError:
1066
1066
  ... # report it — the transaction is still usable
1067
1067
  ```
1068
1068
 
1069
- Forms are the exception: a `ModelForm` pre-checks constraints explicitly (a `validate_constraints()` call in its `_post_clean`) so it can surface every violation at once, then saves with validation already done. A direct `create()`/`update()` reports the first violation Postgres hits.
1069
+ Forms are the exception: a `ModelForm` pre-checks constraints explicitly (a `validate_constraints()` call in its `_post_clean`) so it can surface every violation at once, then writes via `form.create()`/`form.update()` with validation already done. A direct `create()`/`update()` reports the first violation Postgres hits.
1070
1070
 
1071
1071
  This applies to instance writes only. Set-based writes — `QuerySet.update()` and `bulk_create()` — raise the raw `psycopg.IntegrityError`, since there's no instance to attribute the error to. If you retry on a unique conflict, catch both:
1072
1072
 
@@ -1234,7 +1234,7 @@ class UserForm(forms.ModelForm):
1234
1234
  # Usage
1235
1235
  form = UserForm(request=request)
1236
1236
  if form.is_valid():
1237
- user = form.save()
1237
+ user = form.create()
1238
1238
  ```
1239
1239
 
1240
1240
  ## Architecture
@@ -1,5 +1,33 @@
1
1
  # plain-postgres changelog
2
2
 
3
+ ## [0.107.1](https://github.com/dropseed/plain/releases/plain-postgres@0.107.1) (2026-06-08)
4
+
5
+ ### What's changed
6
+
7
+ - Internal: typing-only cleanup for the `ty` 0.0.45 upgrade. `Query.order_by` is now annotated `tuple[Any, ...]` instead of relying on empty-tuple inference, a few field-name dict keys are narrowed via `assert` or the known-`str` loop variable, and `ty: ignore` comments on the intentionally heterogeneous `get_fields()` / `klass_info` structures were adjusted (net removal of several ignores). No runtime behavior changes. ([5c8015795d](https://github.com/dropseed/plain/commit/5c8015795d), [95f54e880d](https://github.com/dropseed/plain/commit/95f54e880d))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required.
12
+
13
+ ## [0.107.0](https://github.com/dropseed/plain/releases/plain-postgres@0.107.0) (2026-06-07)
14
+
15
+ ### What's changed
16
+
17
+ - **`ModelForm.save()` is replaced by explicit `create()` and `update()`**, mirroring the `Model.create()`/`update()` split from 0.106.0. The view already knows whether it's inserting or updating (`CreateView` vs `UpdateView`), so the form no longer branches on `instance._state.adding` to guess. `form.create()` INSERTs the instance (and its m2m data) and returns it; `form.update(fields=None)` UPDATEs it (and its m2m data) and returns it, with `fields=[...]` passed straight through to `Model.update()` to limit the columns written. `save(commit=...)` — including the `commit=False` deferred path — is removed. ([66634f5af9](https://github.com/dropseed/plain/commit/66634f5af9))
18
+
19
+ ### Upgrade instructions
20
+
21
+ - Replace `form.save()` with `form.create()` in create flows and `form.update()` in update flows. The `/plain-upgrade` skill rewrites these for you — the create-vs-update choice is context-dependent, so it reasons per call site.
22
+ - `form.update(fields=[...])` is available to limit the columns written (a pass-through to `Model.update(fields=...)`).
23
+ - For the old `form.save(commit=False)` pattern (set extra non-form attributes, then save), assign those attributes on `form.instance` after `is_valid()`, then call `form.create()` / `form.update()` — m2m data is still saved for you, so the separate `save_m2m()` step is no longer needed:
24
+
25
+ ```python
26
+ if form.is_valid():
27
+ form.instance.user = request.user
28
+ obj = form.create()
29
+ ```
30
+
3
31
  ## [0.106.0](https://github.com/dropseed/plain/releases/plain-postgres@0.106.0) (2026-06-03)
4
32
 
5
33
  ### What's changed
@@ -1053,7 +1053,7 @@ except ValidationError:
1053
1053
  ... # report it — the transaction is still usable
1054
1054
  ```
1055
1055
 
1056
- Forms are the exception: a `ModelForm` pre-checks constraints explicitly (a `validate_constraints()` call in its `_post_clean`) so it can surface every violation at once, then saves with validation already done. A direct `create()`/`update()` reports the first violation Postgres hits.
1056
+ Forms are the exception: a `ModelForm` pre-checks constraints explicitly (a `validate_constraints()` call in its `_post_clean`) so it can surface every violation at once, then writes via `form.create()`/`form.update()` with validation already done. A direct `create()`/`update()` reports the first violation Postgres hits.
1057
1057
 
1058
1058
  This applies to instance writes only. Set-based writes — `QuerySet.update()` and `bulk_create()` — raise the raw `psycopg.IntegrityError`, since there's no instance to attribute the error to. If you retry on a unique conflict, catch both:
1059
1059
 
@@ -1221,7 +1221,7 @@ class UserForm(forms.ModelForm):
1221
1221
  # Usage
1222
1222
  form = UserForm(request=request)
1223
1223
  if form.is_valid():
1224
- user = form.save()
1224
+ user = form.create()
1225
1225
  ```
1226
1226
 
1227
1227
  ## Architecture
@@ -385,7 +385,7 @@ class UniqueConstraint(BaseConstraint):
385
385
  # A composite constraint containing NULL value cannot cause
386
386
  # a violation since NULL != NULL in SQL.
387
387
  return
388
- lookup_kwargs[field.name] = lookup_value
388
+ lookup_kwargs[field_name] = lookup_value
389
389
  queryset = queryset.filter(**lookup_kwargs)
390
390
  else:
391
391
  # Ignore constraints with excluded fields.
@@ -5,6 +5,7 @@ and database field objects.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ from collections.abc import Iterable
8
9
  from itertools import chain
9
10
  from typing import TYPE_CHECKING, Any, cast
10
11
 
@@ -380,30 +381,36 @@ class BaseModelForm(BaseForm):
380
381
  if f.name in cleaned_data:
381
382
  f.save_form_data(self.instance, cleaned_data[f.name])
382
383
 
383
- def save(self, commit: bool = True) -> Any:
384
- """
385
- Save this form's self.instance object if commit=True. Otherwise, add
386
- a save_m2m() method to the form which can be called after the instance
387
- is saved manually at a later time. Return the model instance.
388
- """
384
+ def _raise_if_invalid(self, action: str) -> None:
385
+ """Guard the write methods -- a form with errors can't be persisted."""
389
386
  if self.errors:
390
387
  raise ValueError(
391
- "The {} could not be {} because the data didn't validate.".format(
392
- self.instance.model_options.object_name,
393
- "created" if self.instance._state.adding else "changed",
394
- )
388
+ f"The {self.instance.model_options.object_name} could not be "
389
+ f"{action} because the data didn't validate."
395
390
  )
396
- if commit:
397
- # If committing, persist the instance and the m2m data immediately.
398
- if self.instance._state.adding:
399
- self.instance.create(clean_and_validate=False)
400
- else:
401
- self.instance.update(clean_and_validate=False)
402
- self._save_m2m()
403
- else:
404
- # If not committing, add a method to the form to allow deferred
405
- # saving of m2m data.
406
- self.save_m2m = self._save_m2m
391
+
392
+ def create(self) -> Any:
393
+ """INSERT this form's instance (and its m2m data) and return it.
394
+
395
+ Shape and constraints were already validated in _post_clean, so the
396
+ write trusts them (clean_and_validate=False). This mirrors
397
+ Model.create(); use it from a create flow and update() to UPDATE.
398
+ """
399
+ self._raise_if_invalid("created")
400
+ self.instance.create(clean_and_validate=False)
401
+ self._save_m2m()
402
+ return self.instance
403
+
404
+ def update(self, *, fields: Iterable[str] | None = None) -> Any:
405
+ """UPDATE this form's instance (and its m2m data) and return it.
406
+
407
+ `fields` is passed straight through to Model.update() to limit the
408
+ columns written; the default writes every loaded field. Validation
409
+ already ran in _post_clean, so the write trusts it.
410
+ """
411
+ self._raise_if_invalid("changed")
412
+ self.instance.update(clean_and_validate=False, fields=fields)
413
+ self._save_m2m()
407
414
  return self.instance
408
415
 
409
416
 
@@ -511,8 +511,9 @@ class Meta:
511
511
  fields.append(field.remote_field)
512
512
 
513
513
  if forward:
514
- fields += self.local_fields
515
- fields += self.local_many_to_many
514
+ # get_fields() intentionally returns a heterogeneous list of field types.
515
+ fields += self.local_fields # ty: ignore[unsupported-operator]
516
+ fields += self.local_many_to_many # ty: ignore[unsupported-operator]
516
517
 
517
518
  # In order to avoid list manipulation. Always
518
519
  # return a shallow copy of the results
@@ -47,7 +47,7 @@ class MigrationExecutor:
47
47
  if migration not in applied:
48
48
  plan.append(self.loader.graph.nodes[migration])
49
49
  applied[migration] = self.loader.graph.nodes[migration]
50
- return plan
50
+ return plan # ty: ignore[invalid-return-type] (graph.nodes may hold dummy None, never reached here)
51
51
 
52
52
  def _create_project_state(
53
53
  self, with_applied_migrations: bool = False
@@ -679,6 +679,7 @@ class ModelState:
679
679
  if getattr(field, "remote_field", None) and exclude_rels:
680
680
  continue
681
681
  name = field.name
682
+ assert name is not None
682
683
  try:
683
684
  fields.append((name, field.clone()))
684
685
  except TypeError as e:
@@ -688,6 +689,7 @@ class ModelState:
688
689
  if not exclude_rels:
689
690
  for field in model._model_meta.local_many_to_many:
690
691
  name = field.name
692
+ assert name is not None
691
693
  try:
692
694
  fields.append((name, field.clone()))
693
695
  except TypeError as e:
@@ -699,7 +701,7 @@ class ModelState:
699
701
  bases = []
700
702
  for base in model.__bases__:
701
703
  bases.append(base)
702
- return bases
704
+ return bases # ty: ignore[invalid-return-type] (__bases__ widens to list[type])
703
705
 
704
706
  # We can't rely on __mro__ directly because we only want to flatten
705
707
  # abstract models and not the whole tree. However by recursing on
@@ -809,6 +809,7 @@ class QuerySet[T: "Model"]:
809
809
  case_statement = Case(*when_statements, output_field=field)
810
810
  # PostgreSQL requires casted CASE in updates
811
811
  case_statement = Cast(case_statement, output_field=field)
812
+ assert field.name is not None
812
813
  update_kwargs[field.name] = case_statement
813
814
  updates.append(([obj.id for obj in batch_objs], update_kwargs))
814
815
  rows_updated = 0
@@ -343,7 +343,7 @@ class SQLCompiler:
343
343
  if self.query.select_related:
344
344
  related_klass_infos = self.get_related_selections(select, select_mask)
345
345
  if klass_info is not None:
346
- klass_info["related_klass_infos"] = related_klass_infos
346
+ klass_info["related_klass_infos"] = related_klass_infos # ty: ignore[invalid-assignment] (heterogeneous klass_info dict)
347
347
 
348
348
  ret = []
349
349
  col_idx = 1
@@ -367,7 +367,7 @@ class SQLCompiler:
367
367
  alias = f"col{col_idx}"
368
368
  col_idx += 1
369
369
  ret.append((col, (sql, params), alias))
370
- return ret, klass_info, annotations
370
+ return ret, klass_info, annotations # ty: ignore[invalid-return-type] (heterogeneous klass_info dict)
371
371
 
372
372
  def _order_by_pairs(self) -> Generator[tuple[OrderBy, bool]]:
373
373
  if self.query.extra_order_by:
@@ -229,7 +229,9 @@ class Query(BaseExpression):
229
229
  # - True: group by all select fields of the model
230
230
  # See compiler.get_group_by() for details.
231
231
  group_by = None
232
- order_by = ()
232
+ # Holds field-name strings and order expressions; the compiler duck-types
233
+ # each entry, so the element type is intentionally loose.
234
+ order_by: tuple[Any, ...] = ()
233
235
  low_mark = 0 # Used for offset/limit.
234
236
  high_mark = None # Used for offset/limit.
235
237
  distinct = False
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.postgres"
3
- version = "0.106.0"
3
+ version = "0.107.1"
4
4
  description = "Model your data and store it in a database."
5
5
  authors = [{ name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev" }]
6
6
  readme = "README.md"
@@ -18,6 +18,11 @@ class ExamplesRouter(Router):
18
18
  views.FormsExampleUpdateView,
19
19
  name="forms_update",
20
20
  ),
21
+ path(
22
+ "forms/<int:pk>/delete",
23
+ views.FormsExampleDeleteView,
24
+ name="forms_delete",
25
+ ),
21
26
  path(
22
27
  "child-cascade/create",
23
28
  views.ChildCascadeCreateView,
@@ -4,7 +4,7 @@ import json
4
4
  from typing import Any
5
5
 
6
6
  from plain.http import Response
7
- from plain.templates.views import CreateView, UpdateView
7
+ from plain.templates.views import CreateView, DeleteView, UpdateView
8
8
 
9
9
  from .forms import (
10
10
  ChildCascadeForm,
@@ -52,6 +52,13 @@ class FormsExampleUpdateView(_NoTemplateFormView, UpdateView):
52
52
  return FormsExample.query.filter(id=self.url_kwargs["pk"]).first()
53
53
 
54
54
 
55
+ class FormsExampleDeleteView(_NoTemplateFormView, DeleteView):
56
+ success_url = "/ok/"
57
+
58
+ def get_object(self) -> Any:
59
+ return FormsExample.query.filter(id=self.url_kwargs["pk"]).first()
60
+
61
+
55
62
  class ChildCascadeCreateView(_NoTemplateFormView, CreateView):
56
63
  form_class = ChildCascadeForm
57
64
  success_url = "/ok/"
@@ -181,6 +181,34 @@ class TestFormsExampleUpdate:
181
181
  assert existing.count == 1
182
182
 
183
183
 
184
+ class TestFormsExampleDelete:
185
+ """DeleteView posts an empty confirmation form; the view deletes the row."""
186
+
187
+ def _create_existing(self) -> FormsExample:
188
+ return FormsExample.query.create(
189
+ name="Doomed",
190
+ status="draft",
191
+ count=1,
192
+ ratio=0.5,
193
+ amount=Decimal("1.00"),
194
+ event_date=datetime.date(2020, 1, 1),
195
+ event_time=datetime.time(0, 0, 0),
196
+ event_datetime=datetime.datetime(2020, 1, 1, 0, 0, 0, tzinfo=datetime.UTC),
197
+ duration=datetime.timedelta(minutes=1),
198
+ external_id=uuid.UUID("00000000-0000-0000-0000-000000000002"),
199
+ )
200
+
201
+ def test_delete_removes_the_row(self, db):
202
+ existing = self._create_existing()
203
+ client = Client()
204
+
205
+ response = client.post(f"/examples/forms/{existing.id}/delete")
206
+
207
+ assert response.status_code == 302, response.content
208
+ assert response.headers["Location"] == "/ok/"
209
+ assert not FormsExample.query.filter(id=existing.id).exists()
210
+
211
+
184
212
  class TestForeignKeyRoundTrip:
185
213
  """Exercises the explicit ForeignKeyField → ModelChoiceField handler."""
186
214