plain.postgres 0.106.0__tar.gz → 0.107.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.
Files changed (216) hide show
  1. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/PKG-INFO +3 -3
  2. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/CHANGELOG.md +18 -0
  3. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/README.md +2 -2
  4. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/forms.py +28 -21
  5. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/pyproject.toml +1 -1
  6. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/urls.py +5 -0
  7. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/views.py +8 -1
  8. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_modelform_roundtrip.py +28 -0
  9. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/.gitignore +0 -0
  10. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/CLAUDE.md +0 -0
  11. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/LICENSE +0 -0
  12. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/README.md +0 -0
  13. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/__init__.py +0 -0
  14. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/adapters.py +0 -0
  15. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/agents/.claude/rules/plain-postgres.md +0 -0
  16. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/agents/.claude/skills/plain-postgres-doctor/SKILL.md +0 -0
  17. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/aggregates.py +0 -0
  18. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/base.py +0 -0
  19. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/__init__.py +0 -0
  20. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/converge.py +0 -0
  21. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/core.py +0 -0
  22. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/decorators.py +0 -0
  23. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/diagnose.py +0 -0
  24. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/migrations.py +0 -0
  25. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/schema.py +0 -0
  26. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/cli/sync.py +0 -0
  27. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/config.py +0 -0
  28. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/connection.py +0 -0
  29. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/constants.py +0 -0
  30. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/constraints.py +0 -0
  31. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/convergence/__init__.py +0 -0
  32. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/convergence/analysis.py +0 -0
  33. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/convergence/fixes.py +0 -0
  34. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/convergence/planning.py +0 -0
  35. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/database_url.py +0 -0
  36. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/db.py +0 -0
  37. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/ddl.py +0 -0
  38. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/default_settings.py +0 -0
  39. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/deletion.py +0 -0
  40. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/dialect.py +0 -0
  41. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/entrypoints.py +0 -0
  42. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/enums.py +0 -0
  43. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/exceptions.py +0 -0
  44. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/expressions.py +0 -0
  45. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/__init__.py +0 -0
  46. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/base.py +0 -0
  47. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/binary.py +0 -0
  48. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/boolean.py +0 -0
  49. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/duration.py +0 -0
  50. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/encrypted.py +0 -0
  51. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/json.py +0 -0
  52. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/mixins.py +0 -0
  53. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/network.py +0 -0
  54. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/numeric.py +0 -0
  55. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/primary_key.py +0 -0
  56. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/related.py +0 -0
  57. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/related_descriptors.py +0 -0
  58. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/related_lookups.py +0 -0
  59. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/related_managers.py +0 -0
  60. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/reverse_descriptors.py +0 -0
  61. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/reverse_related.py +0 -0
  62. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/temporal.py +0 -0
  63. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/text.py +0 -0
  64. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/timezones.py +0 -0
  65. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/fields/uuid.py +0 -0
  66. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/__init__.py +0 -0
  67. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/comparison.py +0 -0
  68. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/datetime.py +0 -0
  69. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/math.py +0 -0
  70. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/mixins.py +0 -0
  71. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/random.py +0 -0
  72. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/text.py +0 -0
  73. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/uuid.py +0 -0
  74. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/functions/window.py +0 -0
  75. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/indexes.py +0 -0
  76. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/__init__.py +0 -0
  77. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/__init__.py +0 -0
  78. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/checks_cumulative.py +0 -0
  79. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/checks_snapshot.py +0 -0
  80. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/checks_structural.py +0 -0
  81. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/context.py +0 -0
  82. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/helpers.py +0 -0
  83. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/ownership.py +0 -0
  84. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/runner.py +0 -0
  85. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/health/types.py +0 -0
  86. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/introspection/schema.py +0 -0
  87. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/lookups.py +0 -0
  88. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/meta.py +0 -0
  89. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/middleware.py +0 -0
  90. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/__init__.py +0 -0
  91. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/autodetector.py +0 -0
  92. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/exceptions.py +0 -0
  93. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/executor.py +0 -0
  94. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/graph.py +0 -0
  95. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/loader.py +0 -0
  96. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/migration.py +0 -0
  97. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/operations/__init__.py +0 -0
  98. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/operations/base.py +0 -0
  99. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/operations/fields.py +0 -0
  100. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/operations/models.py +0 -0
  101. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/operations/special.py +0 -0
  102. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/optimizer.py +0 -0
  103. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/questioner.py +0 -0
  104. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/recorder.py +0 -0
  105. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/serializer.py +0 -0
  106. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/state.py +0 -0
  107. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/utils.py +0 -0
  108. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/migrations/writer.py +0 -0
  109. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/options.py +0 -0
  110. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/otel.py +0 -0
  111. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/preflight.py +0 -0
  112. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/query.py +0 -0
  113. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/query_utils.py +0 -0
  114. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/registry.py +0 -0
  115. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/schema.py +0 -0
  116. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sources.py +0 -0
  117. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/__init__.py +0 -0
  118. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/compiler.py +0 -0
  119. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/constants.py +0 -0
  120. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/datastructures.py +0 -0
  121. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/query.py +0 -0
  122. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/sql/where.py +0 -0
  123. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/test/__init__.py +0 -0
  124. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/test/database.py +0 -0
  125. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/test/pytest.py +0 -0
  126. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/transaction.py +0 -0
  127. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/types.py +0 -0
  128. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/types.pyi +0 -0
  129. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/plain/postgres/utils.py +0 -0
  130. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/forms.py +0 -0
  131. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0001_initial.py +0 -0
  132. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  133. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  134. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0004_defaultquerysetmodel_mixintestmodel_and_more.py +0 -0
  135. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0005_feature_carfeature_car_features.py +0 -0
  136. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0006_secretstore.py +0 -0
  137. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0007_treenode_unconstrainedchild.py +0 -0
  138. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0008_setsentinelparent_diamondparenta_midparent_and_more.py +0 -0
  139. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0009_circb_circa_circb_partner.py +0 -0
  140. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0010_hideableitem.py +0 -0
  141. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0011_defaultsexample.py +0 -0
  142. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0012_iterationexample.py +0 -0
  143. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0013_indexexample_constraintexample_nullabilityexample.py +0 -0
  144. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/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.0}/tests/app/examples/migrations/0015_dbdefaultsexample.py +0 -0
  146. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0016_formsexample.py +0 -0
  147. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0017_random_string_token.py +0 -0
  148. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/0018_storageparametersexample.py +0 -0
  149. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/migrations/__init__.py +0 -0
  150. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/__init__.py +0 -0
  151. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/constraints.py +0 -0
  152. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/defaults.py +0 -0
  153. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/delete.py +0 -0
  154. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/encrypted.py +0 -0
  155. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/forms.py +0 -0
  156. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/indexes.py +0 -0
  157. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/iteration.py +0 -0
  158. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/mixins.py +0 -0
  159. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/nullability.py +0 -0
  160. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/querysets.py +0 -0
  161. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/relationships.py +0 -0
  162. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/storage_parameters.py +0 -0
  163. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/trees.py +0 -0
  164. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/examples/models/unregistered.py +0 -0
  165. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/settings.py +0 -0
  166. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/app/urls.py +0 -0
  167. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/conftest.py +0 -0
  168. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/conftest_convergence.py +0 -0
  169. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_autodetector_not_null_errors.py +0 -0
  170. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_autodetector_type_change.py +0 -0
  171. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_connection_isolation.py +0 -0
  172. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_connection_lifecycle.py +0 -0
  173. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_connection_pool.py +0 -0
  174. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_constraint_violation_error.py +0 -0
  175. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence.py +0 -0
  176. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_constraints.py +0 -0
  177. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_defaults.py +0 -0
  178. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_fk.py +0 -0
  179. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_indexes.py +0 -0
  180. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_nullability.py +0 -0
  181. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_storage_parameters.py +0 -0
  182. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_convergence_timeouts.py +0 -0
  183. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_db_expression_defaults.py +0 -0
  184. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_diagnose.py +0 -0
  185. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_executor_connection_hook.py +0 -0
  186. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_fk_characterization.py +0 -0
  187. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_health.py +0 -0
  188. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_introspection.py +0 -0
  189. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_literal_default_persistence.py +0 -0
  190. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_management_connection.py +0 -0
  191. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_migration_executor.py +0 -0
  192. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_no_callable_defaults.py +0 -0
  193. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_otel_metrics.py +0 -0
  194. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_preflight_duplicate_indexes.py +0 -0
  195. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_preflight_fk_coverage.py +0 -0
  196. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_schema_normalize_type.py +0 -0
  197. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/internal/test_schema_timeouts.py +0 -0
  198. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_create_update.py +0 -0
  199. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_database_url.py +0 -0
  200. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_deferred_loading.py +0 -0
  201. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_delete_behaviors.py +0 -0
  202. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_encrypted_fields.py +0 -0
  203. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_exceptions.py +0 -0
  204. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_field_defaults.py +0 -0
  205. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_functions_uuid.py +0 -0
  206. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_integrity_error_mapping.py +0 -0
  207. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_iterator.py +0 -0
  208. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_m2m.py +0 -0
  209. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_manager_assignment.py +0 -0
  210. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_manual_pk.py +0 -0
  211. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_mixins.py +0 -0
  212. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_queryset_repr.py +0 -0
  213. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_random_string_field.py +0 -0
  214. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_raw_query.py +0 -0
  215. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/tests/public/test_read_only_transactions.py +0 -0
  216. {plain_postgres-0.106.0 → plain_postgres-0.107.0}/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.0
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,23 @@
1
1
  # plain-postgres changelog
2
2
 
3
+ ## [0.107.0](https://github.com/dropseed/plain/releases/plain-postgres@0.107.0) (2026-06-07)
4
+
5
+ ### What's changed
6
+
7
+ - **`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))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - 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.
12
+ - `form.update(fields=[...])` is available to limit the columns written (a pass-through to `Model.update(fields=...)`).
13
+ - 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:
14
+
15
+ ```python
16
+ if form.is_valid():
17
+ form.instance.user = request.user
18
+ obj = form.create()
19
+ ```
20
+
3
21
  ## [0.106.0](https://github.com/dropseed/plain/releases/plain-postgres@0.106.0) (2026-06-03)
4
22
 
5
23
  ### 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
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.postgres"
3
- version = "0.106.0"
3
+ version = "0.107.0"
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