plain.models 0.50.0__py3-none-any.whl → 0.51.1__py3-none-any.whl

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 (49) hide show
  1. plain/models/CHANGELOG.md +24 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/backends/base/creation.py +2 -2
  5. plain/models/backends/base/introspection.py +8 -4
  6. plain/models/backends/base/schema.py +89 -71
  7. plain/models/backends/base/validation.py +1 -1
  8. plain/models/backends/mysql/compiler.py +1 -1
  9. plain/models/backends/mysql/operations.py +1 -1
  10. plain/models/backends/mysql/schema.py +4 -4
  11. plain/models/backends/postgresql/operations.py +1 -1
  12. plain/models/backends/postgresql/schema.py +3 -3
  13. plain/models/backends/sqlite3/operations.py +1 -1
  14. plain/models/backends/sqlite3/schema.py +61 -50
  15. plain/models/base.py +116 -163
  16. plain/models/cli.py +4 -4
  17. plain/models/constraints.py +14 -9
  18. plain/models/deletion.py +15 -14
  19. plain/models/expressions.py +21 -5
  20. plain/models/fields/__init__.py +20 -16
  21. plain/models/fields/json.py +3 -3
  22. plain/models/fields/related.py +73 -71
  23. plain/models/fields/related_descriptors.py +2 -2
  24. plain/models/fields/related_lookups.py +1 -1
  25. plain/models/fields/related_managers.py +21 -32
  26. plain/models/fields/reverse_related.py +8 -8
  27. plain/models/forms.py +12 -12
  28. plain/models/indexes.py +5 -4
  29. plain/models/meta.py +505 -0
  30. plain/models/migrations/operations/base.py +1 -1
  31. plain/models/migrations/operations/fields.py +6 -6
  32. plain/models/migrations/operations/models.py +18 -16
  33. plain/models/migrations/recorder.py +9 -5
  34. plain/models/migrations/state.py +35 -46
  35. plain/models/migrations/utils.py +1 -1
  36. plain/models/options.py +182 -518
  37. plain/models/preflight.py +7 -5
  38. plain/models/query.py +119 -65
  39. plain/models/query_utils.py +18 -13
  40. plain/models/registry.py +6 -5
  41. plain/models/sql/compiler.py +51 -37
  42. plain/models/sql/query.py +77 -68
  43. plain/models/sql/subqueries.py +4 -4
  44. plain/models/utils.py +4 -1
  45. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/METADATA +27 -43
  46. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/RECORD +49 -48
  47. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/WHEEL +0 -0
  48. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/entry_points.txt +0 -0
  49. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@ import copy
4
4
  from decimal import Decimal
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from plain.models import register_model
7
+ from plain.models import Options
8
8
  from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
9
9
  from plain.models.backends.ddl_references import Statement
10
10
  from plain.models.backends.utils import strip_quotes
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
17
17
  from plain.models.base import Model
18
18
  from plain.models.constraints import BaseConstraint
19
19
  from plain.models.fields import Field
20
+ from plain.models.fields.related import ManyToManyField
21
+ from plain.models.fields.reverse_related import ManyToManyRel
20
22
 
21
23
 
22
24
  class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -139,7 +141,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
139
141
  if not self._field_should_be_altered(old_field, new_field):
140
142
  return
141
143
  old_field_name = old_field.name
142
- table_name = model._meta.db_table
144
+ table_name = model.model_options.db_table
143
145
  _, old_column_name = old_field.get_attname_column()
144
146
  if (
145
147
  new_field.name != old_field_name
@@ -150,7 +152,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
150
152
  ):
151
153
  if self.connection.in_atomic_block:
152
154
  raise NotSupportedError(
153
- f"Renaming the {model._meta.db_table!r}.{old_field_name!r} column while in a transaction is not "
155
+ f"Renaming the {model.model_options.db_table!r}.{old_field_name!r} column while in a transaction is not "
154
156
  "supported on SQLite < 3.26 because it would break referential "
155
157
  "integrity. Try adding `atomic = False` to the Migration class."
156
158
  )
@@ -217,13 +219,13 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
217
219
  # Work out the new fields dict / mapping
218
220
  body = {
219
221
  f.name: f.clone() if is_self_referential(f) else f
220
- for f in model._meta.local_concrete_fields
222
+ for f in model._model_meta.local_concrete_fields
221
223
  }
222
224
  # Since mapping might mix column names and default values,
223
225
  # its values must be already quoted.
224
226
  mapping = {
225
227
  f.column: self.quote_name(f.column)
226
- for f in model._meta.local_concrete_fields
228
+ for f in model._model_meta.local_concrete_fields
227
229
  }
228
230
  # If any of the new or altered fields is introducing a new PK,
229
231
  # remove the old one
@@ -270,13 +272,13 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
270
272
  # Work inside a new app registry
271
273
  models_registry = ModelsRegistry()
272
274
 
273
- indexes = model._meta.indexes
275
+ indexes = model.model_options.indexes
274
276
  if delete_field:
275
277
  indexes = [
276
278
  index for index in indexes if delete_field.name not in index.fields
277
279
  ]
278
280
 
279
- constraints = list(model._meta.constraints)
281
+ constraints = list(model.model_options.constraints)
280
282
 
281
283
  # Provide isolated instances of the fields to the new model body so
282
284
  # that the existing model's internals aren't interfered with when
@@ -288,32 +290,33 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
288
290
  # table and solely exists for foreign key reference resolution purposes.
289
291
  # This wouldn't be required if the schema editor was operating on model
290
292
  # states instead of rendered models.
291
- meta_contents = {
292
- "package_label": model._meta.package_label,
293
- "db_table": model._meta.db_table,
294
- "indexes": indexes,
295
- "constraints": constraints,
296
- "models_registry": models_registry,
297
- }
298
- meta = type("Meta", (), meta_contents)
299
- body_copy["Meta"] = meta
293
+ meta_options = Options(
294
+ package_label=model.model_options.package_label,
295
+ db_table=model.model_options.db_table,
296
+ indexes=indexes,
297
+ constraints=constraints,
298
+ )
299
+ body_copy["model_options"] = meta_options
300
300
  body_copy["__module__"] = model.__module__
301
- register_model(type(model._meta.object_name, model.__bases__, body_copy)) # type: ignore[arg-type]
301
+ temp_model: type[Model] = type( # type: ignore[assignment]
302
+ model.model_options.object_name, model.__bases__, body_copy
303
+ )
304
+ models_registry.register_model(model.model_options.package_label, temp_model)
302
305
 
303
306
  # Construct a model with a renamed table name.
304
307
  body_copy = copy.deepcopy(body)
305
- meta_contents = {
306
- "package_label": model._meta.package_label,
307
- "db_table": f"new__{strip_quotes(model._meta.db_table)}",
308
- "indexes": indexes,
309
- "constraints": constraints,
310
- "models_registry": models_registry,
311
- }
312
- meta = type("Meta", (), meta_contents)
313
- body_copy["Meta"] = meta
308
+ meta_options = Options(
309
+ package_label=model.model_options.package_label,
310
+ db_table=f"new__{strip_quotes(model.model_options.db_table)}",
311
+ indexes=indexes,
312
+ constraints=constraints,
313
+ )
314
+ body_copy["model_options"] = meta_options
314
315
  body_copy["__module__"] = model.__module__
315
- new_model = type(f"New{model._meta.object_name}", model.__bases__, body_copy)
316
- register_model(new_model) # type: ignore[arg-type]
316
+ new_model: type[Model] = type( # type: ignore[assignment]
317
+ f"New{model.model_options.object_name}", model.__bases__, body_copy
318
+ )
319
+ models_registry.register_model(model.model_options.package_label, new_model)
317
320
 
318
321
  # Create a new table with the updated schema.
319
322
  self.create_model(new_model)
@@ -321,10 +324,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
321
324
  # Copy data from the old table into the new table
322
325
  self.execute(
323
326
  "INSERT INTO {} ({}) SELECT {} FROM {}".format(
324
- self.quote_name(new_model._meta.db_table), # type: ignore[attr-defined]
327
+ self.quote_name(new_model.model_options.db_table),
325
328
  ", ".join(self.quote_name(x) for x in mapping),
326
329
  ", ".join(mapping.values()),
327
- self.quote_name(model._meta.db_table),
330
+ self.quote_name(model.model_options.db_table),
328
331
  )
329
332
  )
330
333
 
@@ -334,8 +337,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
334
337
  # Rename the new table to take way for the old
335
338
  self.alter_db_table(
336
339
  new_model, # type: ignore[arg-type]
337
- new_model._meta.db_table, # type: ignore[attr-defined]
338
- model._meta.db_table,
340
+ new_model.model_options.db_table,
341
+ model.model_options.db_table,
339
342
  disable_constraints=False,
340
343
  )
341
344
 
@@ -355,13 +358,13 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
355
358
  self.execute(
356
359
  self.sql_delete_table
357
360
  % {
358
- "table": self.quote_name(model._meta.db_table),
361
+ "table": self.quote_name(model.model_options.db_table),
359
362
  }
360
363
  )
361
364
  # Remove all deferred statements referencing the deleted table.
362
365
  for sql in list(self.deferred_sql):
363
366
  if isinstance(sql, Statement) and sql.references_table(
364
- model._meta.db_table
367
+ model.model_options.db_table
365
368
  ):
366
369
  self.deferred_sql.remove(sql)
367
370
 
@@ -434,7 +437,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
434
437
  ):
435
438
  return self.execute(
436
439
  self._rename_field_sql(
437
- model._meta.db_table, old_field, new_field, new_type
440
+ model.model_options.db_table, old_field, new_field, new_type
438
441
  )
439
442
  )
440
443
  # Alter by remaking table
@@ -446,8 +449,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
446
449
  old_type != new_type or old_collation != new_collation
447
450
  ):
448
451
  related_models = set()
449
- opts = new_field.model._meta
450
- for remote_field in opts.related_objects:
452
+ meta = new_field.model._model_meta
453
+ for remote_field in meta.related_objects:
451
454
  # Ignore self-relationship since the table was already rebuilt.
452
455
  if remote_field.related_model == model:
453
456
  continue
@@ -455,7 +458,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
455
458
  if remote_field.field_name == new_field.name:
456
459
  related_models.add(remote_field.related_model)
457
460
  if new_field.primary_key:
458
- for many_to_many in opts.many_to_many:
461
+ for many_to_many in meta.many_to_many:
459
462
  # Ignore self-relationship since the table was already rebuilt.
460
463
  if many_to_many.related_model == model:
461
464
  continue
@@ -463,26 +466,34 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
463
466
  self._remake_table(related_model)
464
467
 
465
468
  def _alter_many_to_many(
466
- self, model: type[Model], old_field: Field, new_field: Field, strict: bool
469
+ self,
470
+ model: type[Model],
471
+ old_field: ManyToManyField,
472
+ new_field: ManyToManyField,
473
+ strict: bool,
467
474
  ) -> None:
468
475
  """Alter M2Ms to repoint their to= endpoints."""
476
+ # Type narrow for ManyToManyField.remote_field
477
+ old_rel: ManyToManyRel = old_field.remote_field # type: ignore[assignment]
478
+ new_rel: ManyToManyRel = new_field.remote_field # type: ignore[assignment]
479
+
469
480
  if (
470
- old_field.remote_field.through._meta.db_table # type: ignore[attr-defined]
471
- == new_field.remote_field.through._meta.db_table # type: ignore[attr-defined]
481
+ old_rel.through.model_options.db_table
482
+ == new_rel.through.model_options.db_table
472
483
  ):
473
484
  # The field name didn't change, but some options did, so we have to
474
485
  # propagate this altering.
475
486
  self._remake_table(
476
- old_field.remote_field.through, # type: ignore[attr-defined]
487
+ old_rel.through,
477
488
  alter_fields=[
478
489
  (
479
490
  # The field that points to the target model is needed,
480
491
  # so that table can be remade with the new m2m field -
481
492
  # this is m2m_reverse_field_name().
482
- old_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
493
+ old_rel.through._model_meta.get_field(
483
494
  old_field.m2m_reverse_field_name() # type: ignore[attr-defined]
484
495
  ),
485
- new_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
496
+ new_rel.through._model_meta.get_field(
486
497
  new_field.m2m_reverse_field_name() # type: ignore[attr-defined]
487
498
  ),
488
499
  ),
@@ -490,10 +501,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
490
501
  # The field that points to the model itself is needed,
491
502
  # so that table can be remade with the new self field -
492
503
  # this is m2m_field_name().
493
- old_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
504
+ old_rel.through._model_meta.get_field(
494
505
  old_field.m2m_field_name() # type: ignore[attr-defined]
495
506
  ),
496
- new_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
507
+ new_rel.through._model_meta.get_field(
497
508
  new_field.m2m_field_name() # type: ignore[attr-defined]
498
509
  ),
499
510
  ),
@@ -502,11 +513,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
502
513
  return
503
514
 
504
515
  # Make a new through table
505
- self.create_model(new_field.remote_field.through) # type: ignore[attr-defined]
516
+ self.create_model(new_rel.through)
506
517
  # Copy the data across
507
518
  self.execute(
508
519
  "INSERT INTO {} ({}) SELECT {} FROM {}".format(
509
- self.quote_name(new_field.remote_field.through._meta.db_table), # type: ignore[attr-defined]
520
+ self.quote_name(new_rel.through.model_options.db_table),
510
521
  ", ".join(
511
522
  [
512
523
  "id",
@@ -521,11 +532,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
521
532
  old_field.m2m_reverse_name(), # type: ignore[attr-defined]
522
533
  ]
523
534
  ),
524
- self.quote_name(old_field.remote_field.through._meta.db_table), # type: ignore[attr-defined]
535
+ self.quote_name(old_rel.through.model_options.db_table),
525
536
  )
526
537
  )
527
538
  # Delete the old through table
528
- self.delete_model(old_field.remote_field.through) # type: ignore[attr-defined]
539
+ self.delete_model(old_rel.through)
529
540
 
530
541
  def add_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
531
542
  if isinstance(constraint, UniqueConstraint) and (