plain.models 0.37.0__py3-none-any.whl → 0.39.0__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.
- plain/models/CHANGELOG.md +29 -0
- plain/models/README.md +3 -0
- plain/models/__init__.py +2 -2
- plain/models/backends/base/creation.py +1 -1
- plain/models/backends/base/operations.py +1 -3
- plain/models/backends/base/schema.py +4 -8
- plain/models/backends/mysql/base.py +1 -3
- plain/models/backends/mysql/introspection.py +2 -6
- plain/models/backends/mysql/operations.py +2 -4
- plain/models/backends/postgresql/base.py +2 -6
- plain/models/backends/postgresql/introspection.py +2 -6
- plain/models/backends/postgresql/operations.py +1 -3
- plain/models/backends/postgresql/schema.py +2 -10
- plain/models/backends/sqlite3/base.py +2 -6
- plain/models/backends/sqlite3/introspection.py +2 -8
- plain/models/base.py +46 -74
- plain/models/constraints.py +3 -3
- plain/models/deletion.py +9 -9
- plain/models/fields/__init__.py +30 -104
- plain/models/fields/related.py +90 -343
- plain/models/fields/related_descriptors.py +14 -14
- plain/models/fields/related_lookups.py +2 -2
- plain/models/fields/reverse_related.py +6 -14
- plain/models/forms.py +14 -76
- plain/models/lookups.py +2 -2
- plain/models/migrations/autodetector.py +2 -25
- plain/models/migrations/operations/fields.py +0 -6
- plain/models/migrations/state.py +2 -26
- plain/models/migrations/utils.py +4 -14
- plain/models/options.py +4 -12
- plain/models/query.py +56 -54
- plain/models/query_utils.py +3 -5
- plain/models/sql/compiler.py +16 -18
- plain/models/sql/query.py +12 -11
- plain/models/sql/subqueries.py +10 -10
- {plain_models-0.37.0.dist-info → plain_models-0.39.0.dist-info}/METADATA +4 -1
- {plain_models-0.37.0.dist-info → plain_models-0.39.0.dist-info}/RECORD +40 -40
- {plain_models-0.37.0.dist-info → plain_models-0.39.0.dist-info}/WHEEL +0 -0
- {plain_models-0.37.0.dist-info → plain_models-0.39.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.37.0.dist-info → plain_models-0.39.0.dist-info}/licenses/LICENSE +0 -0
plain/models/deletion.py
CHANGED
@@ -167,8 +167,8 @@ class Collector:
|
|
167
167
|
if model in self.restricted_objects:
|
168
168
|
objs = set(
|
169
169
|
qs.filter(
|
170
|
-
|
171
|
-
obj.
|
170
|
+
id__in=[
|
171
|
+
obj.id
|
172
172
|
for objs in self.restricted_objects[model].values()
|
173
173
|
for obj in objs
|
174
174
|
]
|
@@ -375,7 +375,7 @@ class Collector:
|
|
375
375
|
def delete(self):
|
376
376
|
# sort instance collections
|
377
377
|
for model, instances in self.data.items():
|
378
|
-
self.data[model] = sorted(instances, key=attrgetter("
|
378
|
+
self.data[model] = sorted(instances, key=attrgetter("id"))
|
379
379
|
|
380
380
|
# if possible, bring the models in an order suitable for databases that
|
381
381
|
# don't support transactions or cannot defer constraint checks until the
|
@@ -389,8 +389,8 @@ class Collector:
|
|
389
389
|
instance = list(instances)[0]
|
390
390
|
if self.can_fast_delete(instance):
|
391
391
|
with transaction.mark_for_rollback_on_error():
|
392
|
-
count = sql.DeleteQuery(model).delete_batch([instance.
|
393
|
-
setattr(instance, model._meta.
|
392
|
+
count = sql.DeleteQuery(model).delete_batch([instance.id])
|
393
|
+
setattr(instance, model._meta.get_field("id").attname, None)
|
394
394
|
return count, {model._meta.label: count}
|
395
395
|
|
396
396
|
with transaction.atomic(savepoint=False):
|
@@ -419,7 +419,7 @@ class Collector:
|
|
419
419
|
model = objs[0].__class__
|
420
420
|
query = sql.UpdateQuery(model)
|
421
421
|
query.update_batch(
|
422
|
-
list({obj.
|
422
|
+
list({obj.id for obj in objs}), {field.name: value}
|
423
423
|
)
|
424
424
|
|
425
425
|
# reverse instance collections
|
@@ -429,12 +429,12 @@ class Collector:
|
|
429
429
|
# delete instances
|
430
430
|
for model, instances in self.data.items():
|
431
431
|
query = sql.DeleteQuery(model)
|
432
|
-
|
433
|
-
count = query.delete_batch(
|
432
|
+
id_list = [obj.id for obj in instances]
|
433
|
+
count = query.delete_batch(id_list)
|
434
434
|
if count:
|
435
435
|
deleted_counter[model._meta.label] += count
|
436
436
|
|
437
437
|
for model, instances in self.data.items():
|
438
438
|
for instance in instances:
|
439
|
-
setattr(instance, model._meta.
|
439
|
+
setattr(instance, model._meta.get_field("id").attname, None)
|
440
440
|
return sum(deleted_counter.values()), dict(deleted_counter)
|
plain/models/fields/__init__.py
CHANGED
@@ -30,9 +30,8 @@ from plain.utils.itercompat import is_iterable
|
|
30
30
|
from ..registry import models_registry
|
31
31
|
|
32
32
|
__all__ = [
|
33
|
-
"AutoField",
|
34
33
|
"BLANK_CHOICE_DASH",
|
35
|
-
"
|
34
|
+
"PrimaryKeyField",
|
36
35
|
"BigIntegerField",
|
37
36
|
"BinaryField",
|
38
37
|
"BooleanField",
|
@@ -54,7 +53,6 @@ __all__ = [
|
|
54
53
|
"PositiveBigIntegerField",
|
55
54
|
"PositiveIntegerField",
|
56
55
|
"PositiveSmallIntegerField",
|
57
|
-
"SmallAutoField",
|
58
56
|
"SmallIntegerField",
|
59
57
|
"TextField",
|
60
58
|
"TimeField",
|
@@ -93,9 +91,7 @@ def _load_field(package_label, model_name, field_name):
|
|
93
91
|
# "attname", except if db_column is specified.
|
94
92
|
#
|
95
93
|
# Code that introspects values, or does other dynamic things, should use
|
96
|
-
# attname.
|
97
|
-
#
|
98
|
-
# getattr(obj, opts.pk.attname)
|
94
|
+
# attname.
|
99
95
|
|
100
96
|
|
101
97
|
def _empty(of_cls):
|
@@ -166,7 +162,6 @@ class Field(RegisterLookupMixin):
|
|
166
162
|
def __init__(
|
167
163
|
self,
|
168
164
|
*,
|
169
|
-
primary_key=False,
|
170
165
|
max_length=None,
|
171
166
|
required=True,
|
172
167
|
allow_null=False,
|
@@ -174,13 +169,11 @@ class Field(RegisterLookupMixin):
|
|
174
169
|
default=NOT_PROVIDED,
|
175
170
|
choices=None,
|
176
171
|
db_column=None,
|
177
|
-
auto_created=False,
|
178
172
|
validators=(),
|
179
173
|
error_messages=None,
|
180
174
|
db_comment=None,
|
181
175
|
):
|
182
176
|
self.name = None # Set by set_attributes_from_name
|
183
|
-
self.primary_key = primary_key
|
184
177
|
self.max_length = max_length
|
185
178
|
self.required, self.allow_null = required, allow_null
|
186
179
|
self.remote_field = rel
|
@@ -195,15 +188,13 @@ class Field(RegisterLookupMixin):
|
|
195
188
|
self.choices = choices
|
196
189
|
self.db_column = db_column
|
197
190
|
self.db_comment = db_comment
|
198
|
-
|
191
|
+
|
192
|
+
self.primary_key = False
|
193
|
+
self.auto_created = False
|
199
194
|
|
200
195
|
# Adjust the appropriate creation counter, and save our local copy.
|
201
|
-
|
202
|
-
|
203
|
-
Field.auto_creation_counter -= 1
|
204
|
-
else:
|
205
|
-
self.creation_counter = Field.creation_counter
|
206
|
-
Field.creation_counter += 1
|
196
|
+
self.creation_counter = Field.creation_counter
|
197
|
+
Field.creation_counter += 1
|
207
198
|
|
208
199
|
self._validators = list(validators) # Store for deconstruction later
|
209
200
|
|
@@ -241,7 +232,7 @@ class Field(RegisterLookupMixin):
|
|
241
232
|
def _check_field_name(self):
|
242
233
|
"""
|
243
234
|
Check if field name is valid, i.e. 1) does not end with an
|
244
|
-
underscore, 2) does not contain "__" and 3) is not "
|
235
|
+
underscore, 2) does not contain "__" and 3) is not "id".
|
245
236
|
"""
|
246
237
|
if self.name.endswith("_"):
|
247
238
|
return [
|
@@ -259,10 +250,10 @@ class Field(RegisterLookupMixin):
|
|
259
250
|
id="fields.E002",
|
260
251
|
)
|
261
252
|
]
|
262
|
-
elif self.name == "
|
253
|
+
elif self.name == "id":
|
263
254
|
return [
|
264
255
|
preflight.Error(
|
265
|
-
"'
|
256
|
+
"'id' is a reserved word that cannot be used as a field name.",
|
266
257
|
obj=self,
|
267
258
|
id="fields.E003",
|
268
259
|
)
|
@@ -492,7 +483,6 @@ class Field(RegisterLookupMixin):
|
|
492
483
|
# Short-form way of fetching all the default parameters
|
493
484
|
keywords = {}
|
494
485
|
possibles = {
|
495
|
-
"primary_key": False,
|
496
486
|
"max_length": None,
|
497
487
|
"required": True,
|
498
488
|
"allow_null": False,
|
@@ -500,7 +490,6 @@ class Field(RegisterLookupMixin):
|
|
500
490
|
"choices": None,
|
501
491
|
"db_column": None,
|
502
492
|
"db_comment": None,
|
503
|
-
"auto_created": False,
|
504
493
|
"validators": [],
|
505
494
|
"error_messages": None,
|
506
495
|
}
|
@@ -615,9 +604,9 @@ class Field(RegisterLookupMixin):
|
|
615
604
|
self.name,
|
616
605
|
)
|
617
606
|
|
618
|
-
def
|
607
|
+
def get_id_value_on_save(self, instance):
|
619
608
|
"""
|
620
|
-
Hook to generate new
|
609
|
+
Hook to generate new primary key values on save. This method is called when
|
621
610
|
saving instances with no primary key value set. If this method returns
|
622
611
|
something else than None, then the returned value is used when saving
|
623
612
|
the new instance.
|
@@ -910,7 +899,7 @@ class Field(RegisterLookupMixin):
|
|
910
899
|
choice_func = operator.attrgetter(
|
911
900
|
self.remote_field.get_related_field().attname
|
912
901
|
if hasattr(self.remote_field, "get_related_field")
|
913
|
-
else "
|
902
|
+
else "id"
|
914
903
|
)
|
915
904
|
qs = rel_model._default_manager.complex_filter(limit_choices_to)
|
916
905
|
if ordering:
|
@@ -2234,36 +2223,28 @@ class UUIDField(Field):
|
|
2234
2223
|
return value
|
2235
2224
|
|
2236
2225
|
|
2237
|
-
class
|
2226
|
+
class PrimaryKeyField(BigIntegerField):
|
2238
2227
|
db_returning = True
|
2239
2228
|
|
2240
|
-
def __init__(self
|
2241
|
-
|
2242
|
-
|
2229
|
+
def __init__(self):
|
2230
|
+
super().__init__(required=False)
|
2231
|
+
self.primary_key = True
|
2232
|
+
self.auto_created = True
|
2233
|
+
# Adjust creation counter for auto-created fields
|
2234
|
+
# We need to undo the counter increment from Field.__init__ and use the auto counter
|
2235
|
+
Field.creation_counter -= 1 # Undo the increment
|
2236
|
+
self.creation_counter = Field.auto_creation_counter
|
2237
|
+
Field.auto_creation_counter -= 1
|
2243
2238
|
|
2244
2239
|
def check(self, **kwargs):
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
def _check_primary_key(self):
|
2251
|
-
if not self.primary_key:
|
2252
|
-
return [
|
2253
|
-
preflight.Error(
|
2254
|
-
"AutoFields must set primary_key=True.",
|
2255
|
-
obj=self,
|
2256
|
-
id="fields.E100",
|
2257
|
-
),
|
2258
|
-
]
|
2259
|
-
else:
|
2260
|
-
return []
|
2240
|
+
errors = super().check(**kwargs)
|
2241
|
+
# Remove the E003 error for 'id' field name since PrimaryKeyField is allowed to use it
|
2242
|
+
errors = [e for e in errors if e.id != "fields.E003"]
|
2243
|
+
return errors
|
2261
2244
|
|
2262
2245
|
def deconstruct(self):
|
2263
|
-
|
2264
|
-
|
2265
|
-
kwargs["primary_key"] = True
|
2266
|
-
return name, path, args, kwargs
|
2246
|
+
# PrimaryKeyField takes no parameters, so we return an empty kwargs dict
|
2247
|
+
return (self.name, "plain.models.PrimaryKeyField", [], {})
|
2267
2248
|
|
2268
2249
|
def validate(self, value, model_instance):
|
2269
2250
|
pass
|
@@ -2275,65 +2256,10 @@ class AutoFieldMixin:
|
|
2275
2256
|
return value
|
2276
2257
|
|
2277
2258
|
def contribute_to_class(self, cls, name, **kwargs):
|
2278
|
-
if cls._meta.auto_field:
|
2279
|
-
raise ValueError(
|
2280
|
-
f"Model {cls._meta.label} can't have more than one auto-generated field."
|
2281
|
-
)
|
2282
2259
|
super().contribute_to_class(cls, name, **kwargs)
|
2283
|
-
cls._meta.auto_field = self
|
2284
2260
|
|
2285
|
-
|
2286
|
-
class AutoFieldMeta(type):
|
2287
|
-
"""
|
2288
|
-
Metaclass to maintain backward inheritance compatibility for AutoField.
|
2289
|
-
|
2290
|
-
It is intended that AutoFieldMixin become public API when it is possible to
|
2291
|
-
create a non-integer automatically-generated field using column defaults
|
2292
|
-
stored in the database.
|
2293
|
-
|
2294
|
-
In many areas Plain also relies on using isinstance() to check for an
|
2295
|
-
automatically-generated field as a subclass of AutoField. A new flag needs
|
2296
|
-
to be implemented on Field to be used instead.
|
2297
|
-
|
2298
|
-
When these issues have been addressed, this metaclass could be used to
|
2299
|
-
deprecate inheritance from AutoField and use of isinstance() with AutoField
|
2300
|
-
for detecting automatically-generated fields.
|
2301
|
-
"""
|
2302
|
-
|
2303
|
-
@property
|
2304
|
-
def _subclasses(self):
|
2305
|
-
return (BigAutoField, SmallAutoField)
|
2306
|
-
|
2307
|
-
def __instancecheck__(self, instance):
|
2308
|
-
return isinstance(instance, self._subclasses) or super().__instancecheck__(
|
2309
|
-
instance
|
2310
|
-
)
|
2311
|
-
|
2312
|
-
def __subclasscheck__(self, subclass):
|
2313
|
-
return issubclass(subclass, self._subclasses) or super().__subclasscheck__(
|
2314
|
-
subclass
|
2315
|
-
)
|
2316
|
-
|
2317
|
-
|
2318
|
-
class AutoField(AutoFieldMixin, IntegerField, metaclass=AutoFieldMeta):
|
2319
|
-
def get_internal_type(self):
|
2320
|
-
return "AutoField"
|
2321
|
-
|
2322
|
-
def rel_db_type(self, connection):
|
2323
|
-
return IntegerField().db_type(connection=connection)
|
2324
|
-
|
2325
|
-
|
2326
|
-
class BigAutoField(AutoFieldMixin, BigIntegerField):
|
2327
2261
|
def get_internal_type(self):
|
2328
|
-
return "
|
2262
|
+
return "PrimaryKeyField"
|
2329
2263
|
|
2330
2264
|
def rel_db_type(self, connection):
|
2331
2265
|
return BigIntegerField().db_type(connection=connection)
|
2332
|
-
|
2333
|
-
|
2334
|
-
class SmallAutoField(AutoFieldMixin, SmallIntegerField):
|
2335
|
-
def get_internal_type(self):
|
2336
|
-
return "SmallAutoField"
|
2337
|
-
|
2338
|
-
def rel_db_type(self, connection):
|
2339
|
-
return SmallIntegerField().db_type(connection=connection)
|