plain.models 0.33.1__py3-none-any.whl → 0.34.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 (56) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +8 -10
  3. plain/models/__init__.py +2 -6
  4. plain/models/backends/base/base.py +10 -18
  5. plain/models/backends/base/creation.py +3 -4
  6. plain/models/backends/base/introspection.py +2 -3
  7. plain/models/backends/base/schema.py +3 -9
  8. plain/models/backends/mysql/validation.py +1 -1
  9. plain/models/backends/postgresql/base.py +15 -23
  10. plain/models/backends/postgresql/schema.py +0 -2
  11. plain/models/backends/sqlite3/base.py +1 -1
  12. plain/models/backends/sqlite3/creation.py +2 -2
  13. plain/models/backends/sqlite3/features.py +1 -1
  14. plain/models/backends/sqlite3/schema.py +1 -1
  15. plain/models/backends/utils.py +2 -6
  16. plain/models/backups/core.py +15 -22
  17. plain/models/base.py +179 -225
  18. plain/models/cli.py +25 -62
  19. plain/models/connections.py +48 -165
  20. plain/models/constraints.py +10 -10
  21. plain/models/db.py +7 -15
  22. plain/models/default_settings.py +13 -20
  23. plain/models/deletion.py +14 -16
  24. plain/models/expressions.py +7 -10
  25. plain/models/fields/__init__.py +56 -76
  26. plain/models/fields/json.py +9 -12
  27. plain/models/fields/related.py +5 -17
  28. plain/models/fields/related_descriptors.py +43 -95
  29. plain/models/forms.py +2 -4
  30. plain/models/indexes.py +2 -3
  31. plain/models/lookups.py +0 -7
  32. plain/models/manager.py +1 -14
  33. plain/models/migrations/executor.py +0 -16
  34. plain/models/migrations/loader.py +1 -1
  35. plain/models/migrations/migration.py +1 -1
  36. plain/models/migrations/operations/base.py +4 -11
  37. plain/models/migrations/operations/fields.py +4 -4
  38. plain/models/migrations/operations/models.py +10 -10
  39. plain/models/migrations/operations/special.py +6 -14
  40. plain/models/migrations/recorder.py +1 -1
  41. plain/models/options.py +4 -7
  42. plain/models/preflight.py +25 -44
  43. plain/models/query.py +47 -102
  44. plain/models/query_utils.py +4 -4
  45. plain/models/sql/compiler.py +7 -11
  46. plain/models/sql/query.py +32 -42
  47. plain/models/sql/subqueries.py +6 -8
  48. plain/models/sql/where.py +1 -1
  49. plain/models/test/pytest.py +21 -32
  50. plain/models/test/utils.py +7 -143
  51. plain/models/transaction.py +66 -164
  52. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/METADATA +9 -11
  53. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/RECORD +56 -55
  54. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/WHEEL +0 -0
  55. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/entry_points.txt +0 -0
  56. {plain_models-0.33.1.dist-info → plain_models-0.34.1.dist-info}/licenses/LICENSE +0 -0
plain/models/deletion.py CHANGED
@@ -8,7 +8,7 @@ from plain.models import (
8
8
  sql,
9
9
  transaction,
10
10
  )
11
- from plain.models.db import IntegrityError, connections
11
+ from plain.models.db import IntegrityError, db_connection
12
12
  from plain.models.query import QuerySet
13
13
 
14
14
 
@@ -31,7 +31,7 @@ def CASCADE(collector, field, sub_objs, using):
31
31
  nullable=field.allow_null,
32
32
  fail_on_restricted=False,
33
33
  )
34
- if field.allow_null and not connections[using].features.can_defer_constraint_checks:
34
+ if field.allow_null and not db_connection.features.can_defer_constraint_checks:
35
35
  collector.add_field_update(field, None, sub_objs)
36
36
 
37
37
 
@@ -93,8 +93,7 @@ def get_candidate_relations_to_delete(opts):
93
93
 
94
94
 
95
95
  class Collector:
96
- def __init__(self, using, origin=None):
97
- self.using = using
96
+ def __init__(self, origin=None):
98
97
  # A Model or QuerySet object.
99
98
  self.origin = origin
100
99
  # Initially, {model: {instances}}, later values become lists.
@@ -210,11 +209,12 @@ class Collector:
210
209
 
211
210
  def get_del_batches(self, objs, fields):
212
211
  """
213
- Return the objs in suitably sized batches for the used connection.
212
+ Return the objs in suitably sized batches for the used db_connection.
214
213
  """
215
214
  field_names = [field.name for field in fields]
216
215
  conn_batch_size = max(
217
- connections[self.using].ops.bulk_batch_size(field_names, objs), 1
216
+ db_connection.ops.bulk_batch_size(field_names, objs),
217
+ 1,
218
218
  )
219
219
  if len(objs) > conn_batch_size:
220
220
  return [
@@ -300,7 +300,7 @@ class Collector:
300
300
  sub_objs = sub_objs.only(*tuple(referenced_fields))
301
301
  if getattr(on_delete, "lazy_sub_objs", False) or sub_objs:
302
302
  try:
303
- on_delete(self, field, sub_objs, self.using)
303
+ on_delete(self, field, sub_objs)
304
304
  except ProtectedError as error:
305
305
  key = f"'{field.model.__name__}.{field.name}'"
306
306
  protected_objects[key] += error.protected_objects
@@ -352,7 +352,7 @@ class Collector:
352
352
  [(f"{related_field.name}__in", objs) for related_field in related_fields],
353
353
  connector=query_utils.Q.OR,
354
354
  )
355
- return related_model._base_manager.using(self.using).filter(predicate)
355
+ return related_model._base_manager.filter(predicate)
356
356
 
357
357
  def sort(self):
358
358
  sorted_models = []
@@ -388,17 +388,15 @@ class Collector:
388
388
  if len(self.data) == 1 and len(instances) == 1:
389
389
  instance = list(instances)[0]
390
390
  if self.can_fast_delete(instance):
391
- with transaction.mark_for_rollback_on_error(self.using):
392
- count = sql.DeleteQuery(model).delete_batch(
393
- [instance.pk], self.using
394
- )
391
+ with transaction.mark_for_rollback_on_error():
392
+ count = sql.DeleteQuery(model).delete_batch([instance.pk])
395
393
  setattr(instance, model._meta.pk.attname, None)
396
394
  return count, {model._meta.label: count}
397
395
 
398
- with transaction.atomic(using=self.using, savepoint=False):
396
+ with transaction.atomic(savepoint=False):
399
397
  # fast deletes
400
398
  for qs in self.fast_deletes:
401
- count = qs._raw_delete(using=self.using)
399
+ count = qs._raw_delete()
402
400
  if count:
403
401
  deleted_counter[qs.model._meta.label] += count
404
402
 
@@ -421,7 +419,7 @@ class Collector:
421
419
  model = objs[0].__class__
422
420
  query = sql.UpdateQuery(model)
423
421
  query.update_batch(
424
- list({obj.pk for obj in objs}), {field.name: value}, self.using
422
+ list({obj.pk for obj in objs}), {field.name: value}
425
423
  )
426
424
 
427
425
  # reverse instance collections
@@ -432,7 +430,7 @@ class Collector:
432
430
  for model, instances in self.data.items():
433
431
  query = sql.DeleteQuery(model)
434
432
  pk_list = [obj.pk for obj in instances]
435
- count = query.delete_batch(pk_list, self.using)
433
+ count = query.delete_batch(pk_list)
436
434
  if count:
437
435
  deleted_counter[model._meta.label] += count
438
436
 
@@ -12,10 +12,9 @@ from plain.exceptions import EmptyResultSet, FieldError, FullResultSet
12
12
  from plain.models import fields
13
13
  from plain.models.constants import LOOKUP_SEP
14
14
  from plain.models.db import (
15
- DEFAULT_DB_ALIAS,
16
15
  DatabaseError,
17
16
  NotSupportedError,
18
- connections,
17
+ db_connection,
19
18
  )
20
19
  from plain.models.query_utils import Q
21
20
  from plain.utils.deconstruct import deconstructible
@@ -1782,20 +1781,18 @@ class WindowFrame(Expression):
1782
1781
 
1783
1782
  def __str__(self):
1784
1783
  if self.start.value is not None and self.start.value < 0:
1785
- start = (
1786
- f"{abs(self.start.value)} {connections[DEFAULT_DB_ALIAS].ops.PRECEDING}"
1787
- )
1784
+ start = f"{abs(self.start.value)} {db_connection.ops.PRECEDING}"
1788
1785
  elif self.start.value is not None and self.start.value == 0:
1789
- start = connections[DEFAULT_DB_ALIAS].ops.CURRENT_ROW
1786
+ start = db_connection.ops.CURRENT_ROW
1790
1787
  else:
1791
- start = connections[DEFAULT_DB_ALIAS].ops.UNBOUNDED_PRECEDING
1788
+ start = db_connection.ops.UNBOUNDED_PRECEDING
1792
1789
 
1793
1790
  if self.end.value is not None and self.end.value > 0:
1794
- end = f"{self.end.value} {connections[DEFAULT_DB_ALIAS].ops.FOLLOWING}"
1791
+ end = f"{self.end.value} {db_connection.ops.FOLLOWING}"
1795
1792
  elif self.end.value is not None and self.end.value == 0:
1796
- end = connections[DEFAULT_DB_ALIAS].ops.CURRENT_ROW
1793
+ end = db_connection.ops.CURRENT_ROW
1797
1794
  else:
1798
- end = connections[DEFAULT_DB_ALIAS].ops.UNBOUNDED_FOLLOWING
1795
+ end = db_connection.ops.UNBOUNDED_FOLLOWING
1799
1796
  return self.template % {
1800
1797
  "frame_type": self.frame_type,
1801
1798
  "start": start,
@@ -11,7 +11,7 @@ from functools import cached_property, partialmethod, total_ordering
11
11
 
12
12
  from plain import exceptions, preflight, validators
13
13
  from plain.models.constants import LOOKUP_SEP
14
- from plain.models.db import DEFAULT_DB_ALIAS, connections, router
14
+ from plain.models.db import db_connection
15
15
  from plain.models.enums import ChoicesMeta
16
16
  from plain.models.query_utils import DeferredAttribute, RegisterLookupMixin
17
17
  from plain.utils import timezone
@@ -346,35 +346,29 @@ class Field(RegisterLookupMixin):
346
346
  )
347
347
  ]
348
348
 
349
- def _check_db_comment(self, databases=None, **kwargs):
350
- if not self.db_comment or not databases:
349
+ def _check_db_comment(self, database=False, **kwargs):
350
+ if not self.db_comment or not database:
351
351
  return []
352
352
  errors = []
353
- for db in databases:
354
- if not router.allow_migrate_model(db, self.model):
355
- continue
356
- connection = connections[db]
357
- if not (
358
- connection.features.supports_comments
359
- or "supports_comments" in self.model._meta.required_db_features
360
- ):
361
- errors.append(
362
- preflight.Warning(
363
- f"{connection.display_name} does not support comments on "
364
- f"columns (db_comment).",
365
- obj=self,
366
- id="fields.W163",
367
- )
353
+ if not (
354
+ db_connection.features.supports_comments
355
+ or "supports_comments" in self.model._meta.required_db_features
356
+ ):
357
+ errors.append(
358
+ preflight.Warning(
359
+ f"{db_connection.display_name} does not support comments on "
360
+ f"columns (db_comment).",
361
+ obj=self,
362
+ id="fields.W163",
368
363
  )
364
+ )
369
365
  return errors
370
366
 
371
367
  def _check_null_allowed_for_primary_keys(self):
372
368
  if (
373
369
  self.primary_key
374
370
  and self.allow_null
375
- and not connections[
376
- DEFAULT_DB_ALIAS
377
- ].features.interprets_empty_strings_as_nulls
371
+ and not db_connection.features.interprets_empty_strings_as_nulls
378
372
  ):
379
373
  # We cannot reliably check this for backends like Oracle which
380
374
  # consider NULL and '' to be equal (and thus set up
@@ -393,13 +387,11 @@ class Field(RegisterLookupMixin):
393
387
  else:
394
388
  return []
395
389
 
396
- def _check_backend_specific_checks(self, databases=None, **kwargs):
397
- if databases is None:
390
+ def _check_backend_specific_checks(self, database=False, **kwargs):
391
+ if not database:
398
392
  return []
399
393
  errors = []
400
- for alias in databases:
401
- if router.allow_migrate_model(alias, self.model):
402
- errors.extend(connections[alias].validation.check_field(self, **kwargs))
394
+ errors.extend(db_connection.validation.check_field(self, **kwargs))
403
395
  return errors
404
396
 
405
397
  def _check_validators(self):
@@ -896,9 +888,7 @@ class Field(RegisterLookupMixin):
896
888
  if (
897
889
  not self.empty_strings_allowed
898
890
  or self.allow_null
899
- and not connections[
900
- DEFAULT_DB_ALIAS
901
- ].features.interprets_empty_strings_as_nulls
891
+ and not db_connection.features.interprets_empty_strings_as_nulls
902
892
  ):
903
893
  return return_None
904
894
  return str # return empty string
@@ -1015,17 +1005,17 @@ class CharField(Field):
1015
1005
  return "String (unlimited)"
1016
1006
 
1017
1007
  def check(self, **kwargs):
1018
- databases = kwargs.get("databases") or []
1008
+ database = kwargs.get("database", False)
1019
1009
  return [
1020
1010
  *super().check(**kwargs),
1021
- *self._check_db_collation(databases),
1011
+ *self._check_db_collation(database),
1022
1012
  *self._check_max_length_attribute(**kwargs),
1023
1013
  ]
1024
1014
 
1025
1015
  def _check_max_length_attribute(self, **kwargs):
1026
1016
  if self.max_length is None:
1027
1017
  if (
1028
- connections[DEFAULT_DB_ALIAS].features.supports_unlimited_charfield
1018
+ db_connection.features.supports_unlimited_charfield
1029
1019
  or "supports_unlimited_charfield"
1030
1020
  in self.model._meta.required_db_features
1031
1021
  ):
@@ -1052,26 +1042,22 @@ class CharField(Field):
1052
1042
  else:
1053
1043
  return []
1054
1044
 
1055
- def _check_db_collation(self, databases):
1045
+ def _check_db_collation(self, database):
1056
1046
  errors = []
1057
- for db in databases:
1058
- if not router.allow_migrate_model(db, self.model):
1059
- continue
1060
- connection = connections[db]
1061
- if not (
1062
- self.db_collation is None
1063
- or "supports_collation_on_charfield"
1064
- in self.model._meta.required_db_features
1065
- or connection.features.supports_collation_on_charfield
1066
- ):
1067
- errors.append(
1068
- preflight.Error(
1069
- f"{connection.display_name} does not support a database collation on "
1070
- "CharFields.",
1071
- obj=self,
1072
- id="fields.E190",
1073
- ),
1074
- )
1047
+ if database and not (
1048
+ self.db_collation is None
1049
+ or "supports_collation_on_charfield"
1050
+ in self.model._meta.required_db_features
1051
+ or db_connection.features.supports_collation_on_charfield
1052
+ ):
1053
+ errors.append(
1054
+ preflight.Error(
1055
+ f"{db_connection.display_name} does not support a database collation on "
1056
+ "CharFields.",
1057
+ obj=self,
1058
+ id="fields.E190",
1059
+ ),
1060
+ )
1075
1061
  return errors
1076
1062
 
1077
1063
  def cast_db_type(self, connection):
@@ -1716,12 +1702,10 @@ class IntegerField(Field):
1716
1702
  @cached_property
1717
1703
  def validators(self):
1718
1704
  # These validators can't be added at field initialization time since
1719
- # they're based on values retrieved from `connections[DEFAULT_DB_ALIAS]`.
1705
+ # they're based on values retrieved from the database connection.
1720
1706
  validators_ = super().validators
1721
1707
  internal_type = self.get_internal_type()
1722
- min_value, max_value = connections[DEFAULT_DB_ALIAS].ops.integer_field_range(
1723
- internal_type
1724
- )
1708
+ min_value, max_value = db_connection.ops.integer_field_range(internal_type)
1725
1709
  if min_value is not None and not any(
1726
1710
  (
1727
1711
  isinstance(validator, validators.MinValueValidator)
@@ -1993,32 +1977,28 @@ class TextField(Field):
1993
1977
  self.db_collation = db_collation
1994
1978
 
1995
1979
  def check(self, **kwargs):
1996
- databases = kwargs.get("databases") or []
1980
+ database = kwargs.get("database", False)
1997
1981
  return [
1998
1982
  *super().check(**kwargs),
1999
- *self._check_db_collation(databases),
1983
+ *self._check_db_collation(database),
2000
1984
  ]
2001
1985
 
2002
- def _check_db_collation(self, databases):
1986
+ def _check_db_collation(self, database):
2003
1987
  errors = []
2004
- for db in databases:
2005
- if not router.allow_migrate_model(db, self.model):
2006
- continue
2007
- connection = connections[db]
2008
- if not (
2009
- self.db_collation is None
2010
- or "supports_collation_on_textfield"
2011
- in self.model._meta.required_db_features
2012
- or connection.features.supports_collation_on_textfield
2013
- ):
2014
- errors.append(
2015
- preflight.Error(
2016
- f"{connection.display_name} does not support a database collation on "
2017
- "TextFields.",
2018
- obj=self,
2019
- id="fields.E190",
2020
- ),
2021
- )
1988
+ if database and not (
1989
+ self.db_collation is None
1990
+ or "supports_collation_on_textfield"
1991
+ in self.model._meta.required_db_features
1992
+ or db_connection.features.supports_collation_on_textfield
1993
+ ):
1994
+ errors.append(
1995
+ preflight.Error(
1996
+ f"{db_connection.display_name} does not support a database collation on "
1997
+ "TextFields.",
1998
+ obj=self,
1999
+ id="fields.E190",
2000
+ ),
2001
+ )
2022
2002
  return errors
2023
2003
 
2024
2004
  def db_parameters(self, connection):
@@ -3,7 +3,7 @@ import json
3
3
  from plain import exceptions, preflight
4
4
  from plain.models import expressions, lookups
5
5
  from plain.models.constants import LOOKUP_SEP
6
- from plain.models.db import NotSupportedError, connections, router
6
+ from plain.models.db import NotSupportedError, db_connection
7
7
  from plain.models.fields import TextField
8
8
  from plain.models.lookups import (
9
9
  FieldGetDbPrepValueMixin,
@@ -42,28 +42,25 @@ class JSONField(CheckFieldDefaultMixin, Field):
42
42
 
43
43
  def check(self, **kwargs):
44
44
  errors = super().check(**kwargs)
45
- databases = kwargs.get("databases") or []
46
- errors.extend(self._check_supported(databases))
45
+ database = kwargs.get("database", False)
46
+ errors.extend(self._check_supported(database))
47
47
  return errors
48
48
 
49
- def _check_supported(self, databases):
49
+ def _check_supported(self, database):
50
50
  errors = []
51
- for db in databases:
52
- if not router.allow_migrate_model(db, self.model):
53
- continue
54
- connection = connections[db]
51
+ if database:
55
52
  if (
56
53
  self.model._meta.required_db_vendor
57
- and self.model._meta.required_db_vendor != connection.vendor
54
+ and self.model._meta.required_db_vendor != db_connection.vendor
58
55
  ):
59
- continue
56
+ return errors
60
57
  if not (
61
58
  "supports_json_field" in self.model._meta.required_db_features
62
- or connection.features.supports_json_field
59
+ or db_connection.features.supports_json_field
63
60
  ):
64
61
  errors.append(
65
62
  preflight.Error(
66
- f"{connection.display_name} does not support JSONFields.",
63
+ f"{db_connection.display_name} does not support JSONFields.",
67
64
  obj=self.model,
68
65
  id="fields.E180",
69
66
  )
@@ -4,11 +4,10 @@ from functools import cached_property, partial
4
4
 
5
5
  from plain import exceptions, preflight
6
6
  from plain.models.constants import LOOKUP_SEP
7
- from plain.models.db import router
8
7
  from plain.models.deletion import SET_DEFAULT, SET_NULL
9
8
  from plain.models.query_utils import PathInfo, Q
10
9
  from plain.models.utils import make_model_tuple
11
- from plain.runtime import SettingsReference, settings
10
+ from plain.runtime import SettingsReference
12
11
 
13
12
  from ..registry import models_registry
14
13
  from . import Field
@@ -929,8 +928,7 @@ class ForeignKey(ForeignObject):
929
928
  if value is None:
930
929
  return
931
930
 
932
- using = router.db_for_read(self.remote_field.model, instance=model_instance)
933
- qs = self.remote_field.model._base_manager.using(using).filter(
931
+ qs = self.remote_field.model._base_manager.filter(
934
932
  **{self.remote_field.field_name: value}
935
933
  )
936
934
  qs = qs.complex_filter(self.get_limit_choices_to())
@@ -1373,23 +1371,13 @@ class ManyToManyField(RelatedField):
1373
1371
  != self.remote_field.through._meta.concrete_model
1374
1372
  ):
1375
1373
  clashing_obj = model._meta.label
1376
- if settings.DATABASE_ROUTERS:
1377
- error_class, error_id = preflight.Warning, "fields.W344"
1378
- error_hint = (
1379
- "You have configured settings.DATABASE_ROUTERS. Verify "
1380
- f"that the table of {clashing_obj!r} is correctly routed to a separate "
1381
- "database."
1382
- )
1383
- else:
1384
- error_class, error_id = preflight.Error, "fields.E340"
1385
- error_hint = None
1386
1374
  return [
1387
- error_class(
1375
+ preflight.Error(
1388
1376
  f"The field's intermediary table '{m2m_db_table}' clashes with the "
1389
1377
  f"table name of '{clashing_obj}'.",
1390
1378
  obj=self,
1391
- hint=error_hint,
1392
- id=error_id,
1379
+ hint=None,
1380
+ id="fields.E340",
1393
1381
  )
1394
1382
  ]
1395
1383
  return []