plain.models 0.41.0__py3-none-any.whl → 0.42.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 +27 -0
- plain/models/README.md +65 -22
- plain/models/__init__.py +0 -2
- plain/models/base.py +12 -34
- plain/models/cli.py +16 -22
- plain/models/constraints.py +1 -1
- plain/models/deletion.py +1 -1
- plain/models/fields/__init__.py +1 -1
- plain/models/fields/related.py +1 -4
- plain/models/fields/related_descriptors.py +57 -53
- plain/models/fields/related_lookups.py +2 -2
- plain/models/fields/reverse_related.py +1 -1
- plain/models/forms.py +1 -1
- plain/models/migrations/autodetector.py +0 -18
- plain/models/migrations/operations/__init__.py +0 -2
- plain/models/migrations/operations/models.py +3 -57
- plain/models/migrations/operations/special.py +2 -8
- plain/models/migrations/recorder.py +1 -1
- plain/models/migrations/serializer.py +0 -12
- plain/models/migrations/state.py +1 -55
- plain/models/options.py +23 -86
- plain/models/otel.py +13 -1
- plain/models/query.py +10 -41
- plain/models/query_utils.py +1 -1
- plain/models/sql/compiler.py +5 -5
- plain/models/sql/query.py +2 -2
- {plain_models-0.41.0.dist-info → plain_models-0.42.0.dist-info}/METADATA +66 -23
- {plain_models-0.41.0.dist-info → plain_models-0.42.0.dist-info}/RECORD +31 -32
- plain/models/manager.py +0 -176
- {plain_models-0.41.0.dist-info → plain_models-0.42.0.dist-info}/WHEEL +0 -0
- {plain_models-0.41.0.dist-info → plain_models-0.42.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.41.0.dist-info → plain_models-0.42.0.dist-info}/licenses/LICENSE +0 -0
plain/models/CHANGELOG.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
# plain-models changelog
|
2
2
|
|
3
|
+
## [0.42.0](https://github.com/dropseed/plain/releases/plain-models@0.42.0) (2025-09-12)
|
4
|
+
|
5
|
+
### What's changed
|
6
|
+
|
7
|
+
- The model manager interface has been renamed from `.objects` to `.query` ([037a239](https://github.com/dropseed/plain/commit/037a239ef4))
|
8
|
+
- Manager functionality has been merged into QuerySet, simplifying the architecture - custom QuerySets can now be set directly via `Meta.queryset_class` ([bbaee93](https://github.com/dropseed/plain/commit/bbaee93839))
|
9
|
+
- The `objects` manager is now set directly on the Model class for better type checking ([fccc5be](https://github.com/dropseed/plain/commit/fccc5be13e))
|
10
|
+
- Database backups are now created automatically during migrations when in DEBUG mode ([c8023074](https://github.com/dropseed/plain/commit/c8023074e9))
|
11
|
+
- Removed several legacy manager features: `default_related_name`, `base_manager_name`, `creation_counter`, `use_in_migrations`, `auto_created`, and routing hints ([multiple commits](https://github.com/dropseed/plain/compare/plain-models@0.41.1...037a239ef4))
|
12
|
+
|
13
|
+
### Upgrade instructions
|
14
|
+
|
15
|
+
- Replace all usage of `Model.objects` with `Model.query` in your codebase (e.g., `User.objects.filter()` becomes `User.query.filter()`)
|
16
|
+
- If you have custom managers, convert them to custom QuerySets and set them using `Meta.queryset_class` instead of assigning to class attributes (if there is more than one custom manager on a class, invoke the new QuerySet class directly or add a shortcut on the Model using `@classmethod`)
|
17
|
+
- Remove any usage of the removed manager features: `default_related_name`, `base_manager_name`, manager `creation_counter`, `use_in_migrations`, `auto_created`, and database routing hints
|
18
|
+
- Any reverse accessors (typically `<related_model>_set` or defined by `related_name`) will now return a manager class for the additional `add()`, `remove()`, `clear()`, etc. methods and the regular queryset methods will be available via `.query` (e.g., `user.articles.first()` becomes `user.articles.query.first()`)
|
19
|
+
|
20
|
+
## [0.41.1](https://github.com/dropseed/plain/releases/plain-models@0.41.1) (2025-09-09)
|
21
|
+
|
22
|
+
### What's changed
|
23
|
+
|
24
|
+
- Improved stack trace filtering in OpenTelemetry spans to exclude internal plain/models frames, making debugging traces cleaner and more focused on user code ([5771dd5](https://github.com/dropseed/plain/commit/5771dd5))
|
25
|
+
|
26
|
+
### Upgrade instructions
|
27
|
+
|
28
|
+
- No changes required
|
29
|
+
|
3
30
|
## [0.41.0](https://github.com/dropseed/plain/releases/plain-models@0.41.0) (2025-09-09)
|
4
31
|
|
5
32
|
### What's changed
|
plain/models/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
- [Fields](#fields)
|
10
10
|
- [Validation](#validation)
|
11
11
|
- [Indexes and constraints](#indexes-and-constraints)
|
12
|
-
- [
|
12
|
+
- [Custom QuerySets](#custom-querysets)
|
13
13
|
- [Forms](#forms)
|
14
14
|
- [Sharing fields across models](#sharing-fields-across-models)
|
15
15
|
- [Installation](#installation)
|
@@ -43,7 +43,7 @@ from .models import User
|
|
43
43
|
|
44
44
|
|
45
45
|
# Create a new user
|
46
|
-
user = User.
|
46
|
+
user = User.query.create(
|
47
47
|
email="test@example.com",
|
48
48
|
password="password",
|
49
49
|
)
|
@@ -56,7 +56,7 @@ user.save()
|
|
56
56
|
user.delete()
|
57
57
|
|
58
58
|
# Query for users
|
59
|
-
admin_users = User.
|
59
|
+
admin_users = User.query.filter(is_admin=True)
|
60
60
|
```
|
61
61
|
|
62
62
|
## Database connection
|
@@ -85,30 +85,30 @@ Multiple backends are supported, including Postgres, MySQL, and SQLite.
|
|
85
85
|
|
86
86
|
## Querying
|
87
87
|
|
88
|
-
Models come with a powerful query API through their [`
|
88
|
+
Models come with a powerful query API through their [`QuerySet`](./query.py#QuerySet) interface:
|
89
89
|
|
90
90
|
```python
|
91
91
|
# Get all users
|
92
|
-
all_users = User.
|
92
|
+
all_users = User.query.all()
|
93
93
|
|
94
94
|
# Filter users
|
95
|
-
admin_users = User.
|
96
|
-
recent_users = User.
|
95
|
+
admin_users = User.query.filter(is_admin=True)
|
96
|
+
recent_users = User.query.filter(created_at__gte=datetime.now() - timedelta(days=7))
|
97
97
|
|
98
98
|
# Get a single user
|
99
|
-
user = User.
|
99
|
+
user = User.query.get(email="test@example.com")
|
100
100
|
|
101
101
|
# Complex queries with Q objects
|
102
102
|
from plain.models import Q
|
103
|
-
users = User.
|
103
|
+
users = User.query.filter(
|
104
104
|
Q(is_admin=True) | Q(email__endswith="@example.com")
|
105
105
|
)
|
106
106
|
|
107
107
|
# Ordering
|
108
|
-
users = User.
|
108
|
+
users = User.query.order_by("-created_at")
|
109
109
|
|
110
110
|
# Limiting results
|
111
|
-
first_10_users = User.
|
111
|
+
first_10_users = User.query.all()[:10]
|
112
112
|
```
|
113
113
|
|
114
114
|
For more advanced querying options, see the [`QuerySet`](./query.py#QuerySet) class.
|
@@ -211,28 +211,71 @@ class User(models.Model):
|
|
211
211
|
]
|
212
212
|
```
|
213
213
|
|
214
|
-
##
|
214
|
+
## Custom QuerySets
|
215
215
|
|
216
|
-
[`
|
216
|
+
With the Manager functionality now merged into QuerySet, you can customize [`QuerySet`](./query.py#QuerySet) classes to provide specialized query methods. There are several ways to use custom QuerySets:
|
217
|
+
|
218
|
+
### Setting a default QuerySet for a model
|
219
|
+
|
220
|
+
Use `Meta.queryset_class` to set a custom QuerySet that will be used by `Model.query`:
|
221
|
+
|
222
|
+
```python
|
223
|
+
class PublishedQuerySet(models.QuerySet):
|
224
|
+
def published_only(self):
|
225
|
+
return self.filter(status="published")
|
226
|
+
|
227
|
+
def draft_only(self):
|
228
|
+
return self.filter(status="draft")
|
229
|
+
|
230
|
+
@models.register_model
|
231
|
+
class Article(models.Model):
|
232
|
+
title = models.CharField(max_length=200)
|
233
|
+
status = models.CharField(max_length=20)
|
234
|
+
|
235
|
+
class Meta:
|
236
|
+
queryset_class = PublishedQuerySet
|
237
|
+
|
238
|
+
# Usage - all methods available on Article.objects
|
239
|
+
all_articles = Article.query.all()
|
240
|
+
published_articles = Article.query.published_only()
|
241
|
+
draft_articles = Article.query.draft_only()
|
242
|
+
```
|
243
|
+
|
244
|
+
### Using custom QuerySets without formal attachment
|
245
|
+
|
246
|
+
You can also use custom QuerySets manually without setting them as the default:
|
217
247
|
|
218
248
|
```python
|
219
|
-
class
|
220
|
-
def
|
221
|
-
return
|
249
|
+
class SpecialQuerySet(models.QuerySet):
|
250
|
+
def special_filter(self):
|
251
|
+
return self.filter(special=True)
|
222
252
|
|
253
|
+
# Create and use the QuerySet manually
|
254
|
+
special_qs = SpecialQuerySet(model=Article)
|
255
|
+
special_articles = special_qs.special_filter()
|
256
|
+
```
|
257
|
+
|
258
|
+
### Using classmethods for convenience
|
259
|
+
|
260
|
+
For even cleaner API, add classmethods to your model:
|
261
|
+
|
262
|
+
```python
|
263
|
+
@models.register_model
|
223
264
|
class Article(models.Model):
|
224
265
|
title = models.CharField(max_length=200)
|
225
266
|
status = models.CharField(max_length=20)
|
226
267
|
|
227
|
-
|
228
|
-
|
268
|
+
@classmethod
|
269
|
+
def published(cls):
|
270
|
+
return PublishedQuerySet(model=cls).published_only()
|
229
271
|
|
230
|
-
|
231
|
-
|
272
|
+
@classmethod
|
273
|
+
def drafts(cls):
|
274
|
+
return PublishedQuerySet(model=cls).draft_only()
|
232
275
|
|
233
276
|
# Usage
|
234
|
-
|
235
|
-
|
277
|
+
published_articles = Article.published()
|
278
|
+
draft_articles = Article.drafts()
|
236
279
|
```
|
237
280
|
|
238
281
|
## Forms
|
plain/models/__init__.py
CHANGED
@@ -59,7 +59,6 @@ from .fields.json import JSONField
|
|
59
59
|
from .indexes import * # NOQA
|
60
60
|
from .indexes import __all__ as indexes_all
|
61
61
|
from .lookups import Lookup, Transform
|
62
|
-
from .manager import Manager
|
63
62
|
from .query import Prefetch, QuerySet, prefetch_related_objects
|
64
63
|
from .query_utils import FilteredRelation, Q
|
65
64
|
from .registry import models_registry, register_model
|
@@ -108,7 +107,6 @@ __all__ += [
|
|
108
107
|
"JSONField",
|
109
108
|
"Lookup",
|
110
109
|
"Transform",
|
111
|
-
"Manager",
|
112
110
|
"Prefetch",
|
113
111
|
"Q",
|
114
112
|
"QuerySet",
|
plain/models/base.py
CHANGED
@@ -24,7 +24,6 @@ from plain.models.deletion import Collector
|
|
24
24
|
from plain.models.expressions import RawSQL, Value
|
25
25
|
from plain.models.fields import NOT_PROVIDED
|
26
26
|
from plain.models.fields.reverse_related import ForeignObjectRel
|
27
|
-
from plain.models.manager import Manager
|
28
27
|
from plain.models.options import Options
|
29
28
|
from plain.models.query import F, Q
|
30
29
|
from plain.packages import packages_registry
|
@@ -75,7 +74,7 @@ class ModelBase(type):
|
|
75
74
|
new_class._add_exceptions()
|
76
75
|
|
77
76
|
# Now go back over all the attrs on this class see if they have a contribute_to_class() method.
|
78
|
-
# Attributes with contribute_to_class are fields
|
77
|
+
# Attributes with contribute_to_class are fields and meta options.
|
79
78
|
for attr_name, attr_value in inspect.getmembers(new_class):
|
80
79
|
if attr_name.startswith("_"):
|
81
80
|
continue
|
@@ -161,16 +160,6 @@ class ModelBase(type):
|
|
161
160
|
", ".join(f.name for f in opts.fields),
|
162
161
|
)
|
163
162
|
|
164
|
-
if not opts.managers:
|
165
|
-
if any(f.name == "objects" for f in opts.fields):
|
166
|
-
raise ValueError(
|
167
|
-
f"Model {cls.__name__} must specify a custom Manager, because it has a "
|
168
|
-
"field named 'objects'."
|
169
|
-
)
|
170
|
-
manager = Manager()
|
171
|
-
manager.auto_created = True
|
172
|
-
cls.add_to_class("objects", manager)
|
173
|
-
|
174
163
|
# Set the name of _meta.indexes. This can't be done in
|
175
164
|
# Options.contribute_to_class() because fields haven't been added to
|
176
165
|
# the model at that point.
|
@@ -179,12 +168,8 @@ class ModelBase(type):
|
|
179
168
|
index.set_name_with_model(cls)
|
180
169
|
|
181
170
|
@property
|
182
|
-
def
|
183
|
-
return cls._meta.
|
184
|
-
|
185
|
-
@property
|
186
|
-
def _default_manager(cls):
|
187
|
-
return cls._meta.default_manager
|
171
|
+
def query(cls):
|
172
|
+
return cls._meta.queryset
|
188
173
|
|
189
174
|
|
190
175
|
class ModelStateFieldsCacheDescriptor:
|
@@ -207,6 +192,9 @@ class ModelState:
|
|
207
192
|
|
208
193
|
|
209
194
|
class Model(metaclass=ModelBase):
|
195
|
+
DoesNotExist: type[ObjectDoesNotExist]
|
196
|
+
MultipleObjectsReturned: type[MultipleObjectsReturned]
|
197
|
+
|
210
198
|
def __init__(self, *args, **kwargs):
|
211
199
|
# Alias some things as locals to avoid repeat global lookups
|
212
200
|
cls = self.__class__
|
@@ -434,7 +422,7 @@ class Model(metaclass=ModelBase):
|
|
434
422
|
"are not allowed in fields."
|
435
423
|
)
|
436
424
|
|
437
|
-
db_instance_qs = self.__class__.
|
425
|
+
db_instance_qs = self.__class__._meta.base_queryset.filter(id=self.id)
|
438
426
|
|
439
427
|
# Use provided fields, if not set then reload all non-deferred fields.
|
440
428
|
deferred_fields = self.get_deferred_fields()
|
@@ -617,7 +605,7 @@ class Model(metaclass=ModelBase):
|
|
617
605
|
force_insert = True
|
618
606
|
# If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
|
619
607
|
if id_set and not force_insert:
|
620
|
-
base_qs =
|
608
|
+
base_qs = meta.base_queryset
|
621
609
|
values = [
|
622
610
|
(
|
623
611
|
f,
|
@@ -641,7 +629,7 @@ class Model(metaclass=ModelBase):
|
|
641
629
|
fields = [f for f in fields if f is not id_field]
|
642
630
|
|
643
631
|
returning_fields = meta.db_returning_fields
|
644
|
-
results = self._do_insert(
|
632
|
+
results = self._do_insert(meta.base_queryset, fields, returning_fields, raw)
|
645
633
|
if results:
|
646
634
|
for value, field in zip(results[0], returning_fields):
|
647
635
|
setattr(self, field.attname, value)
|
@@ -738,7 +726,7 @@ class Model(metaclass=ModelBase):
|
|
738
726
|
q = Q.create([(field.name, param), (f"id__{op}", self.id)], connector=Q.AND)
|
739
727
|
q = Q.create([q, (f"{field.name}__{op}", param)], connector=Q.OR)
|
740
728
|
qs = (
|
741
|
-
self.__class__.
|
729
|
+
self.__class__.query.filter(**kwargs)
|
742
730
|
.filter(q)
|
743
731
|
.order_by(f"{order}{field.name}", f"{order}id")
|
744
732
|
)
|
@@ -836,7 +824,7 @@ class Model(metaclass=ModelBase):
|
|
836
824
|
if len(unique_check) != len(lookup_kwargs):
|
837
825
|
continue
|
838
826
|
|
839
|
-
qs = model_class.
|
827
|
+
qs = model_class.query.filter(**lookup_kwargs)
|
840
828
|
|
841
829
|
# Exclude the current object from the query if we are editing an
|
842
830
|
# instance (as opposed to creating a new one)
|
@@ -995,9 +983,7 @@ class Model(metaclass=ModelBase):
|
|
995
983
|
|
996
984
|
@classmethod
|
997
985
|
def check(cls, **kwargs):
|
998
|
-
errors = [
|
999
|
-
*cls._check_managers(**kwargs),
|
1000
|
-
]
|
986
|
+
errors = []
|
1001
987
|
|
1002
988
|
database = kwargs.get("database", False)
|
1003
989
|
errors += [
|
@@ -1045,14 +1031,6 @@ class Model(metaclass=ModelBase):
|
|
1045
1031
|
)
|
1046
1032
|
return errors
|
1047
1033
|
|
1048
|
-
@classmethod
|
1049
|
-
def _check_managers(cls, **kwargs):
|
1050
|
-
"""Perform all manager checks."""
|
1051
|
-
errors = []
|
1052
|
-
for manager in cls._meta.managers:
|
1053
|
-
errors.extend(manager.check(**kwargs))
|
1054
|
-
return errors
|
1055
|
-
|
1056
1034
|
@classmethod
|
1057
1035
|
def _check_fields(cls, **kwargs):
|
1058
1036
|
"""Perform all field checks."""
|
plain/models/cli.py
CHANGED
@@ -479,7 +479,7 @@ def migrate(
|
|
479
479
|
"Migrations can be pruned only when a package is specified."
|
480
480
|
)
|
481
481
|
if verbosity > 0:
|
482
|
-
click.
|
482
|
+
click.secho("Pruning migrations:", fg="cyan")
|
483
483
|
to_prune = set(executor.loader.applied_migrations) - set(
|
484
484
|
executor.loader.disk_migrations
|
485
485
|
)
|
@@ -529,16 +529,16 @@ def migrate(
|
|
529
529
|
migration_plan = executor.migration_plan(targets)
|
530
530
|
|
531
531
|
if plan:
|
532
|
-
click.
|
532
|
+
click.secho("Planned operations:", fg="cyan")
|
533
533
|
if not migration_plan:
|
534
534
|
click.echo(" No planned migration operations.")
|
535
535
|
else:
|
536
536
|
for migration in migration_plan:
|
537
|
-
click.
|
537
|
+
click.secho(str(migration), fg="cyan")
|
538
538
|
for operation in migration.operations:
|
539
539
|
message, is_error = describe_operation(operation)
|
540
540
|
if is_error:
|
541
|
-
click.
|
541
|
+
click.secho(" " + message, fg="yellow")
|
542
542
|
else:
|
543
543
|
click.echo(" " + message)
|
544
544
|
if check_unapplied:
|
@@ -555,18 +555,18 @@ def migrate(
|
|
555
555
|
|
556
556
|
# Print some useful info
|
557
557
|
if verbosity >= 1:
|
558
|
-
click.
|
558
|
+
click.secho("Operations to perform:", fg="cyan")
|
559
559
|
|
560
560
|
if target_package_labels_only:
|
561
|
-
click.
|
561
|
+
click.secho(
|
562
562
|
" Apply all migrations: "
|
563
563
|
+ (", ".join(sorted({a for a, n in targets})) or "(none)"),
|
564
|
-
|
564
|
+
fg="yellow",
|
565
565
|
)
|
566
566
|
else:
|
567
|
-
click.
|
567
|
+
click.secho(
|
568
568
|
f" Target specific migration: {targets[0][1]}, from {targets[0][0]}",
|
569
|
-
|
569
|
+
fg="yellow",
|
570
570
|
)
|
571
571
|
|
572
572
|
pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
|
@@ -575,30 +575,24 @@ def migrate(
|
|
575
575
|
# pprint(sql)
|
576
576
|
|
577
577
|
if migration_plan:
|
578
|
-
if backup or (
|
579
|
-
backup is None
|
580
|
-
and settings.DEBUG
|
581
|
-
and (
|
582
|
-
no_input
|
583
|
-
or click.confirm(
|
584
|
-
"\nYou are in DEBUG mode. Would you like to make a database backup before running migrations?",
|
585
|
-
default=True,
|
586
|
-
)
|
587
|
-
)
|
588
|
-
):
|
578
|
+
if backup or (backup is None and settings.DEBUG):
|
589
579
|
backup_name = f"migrate_{time.strftime('%Y%m%d_%H%M%S')}"
|
580
|
+
click.secho(
|
581
|
+
f"Creating backup before applying migrations: {backup_name}",
|
582
|
+
bold=True,
|
583
|
+
)
|
590
584
|
# Can't use ctx.invoke because this is called by the test db creation currently,
|
591
585
|
# which doesn't have a context.
|
592
586
|
create_backup.callback(
|
593
587
|
backup_name=backup_name,
|
594
588
|
pg_dump=os.environ.get(
|
595
589
|
"PG_DUMP", "pg_dump"
|
596
|
-
), # Have to this
|
590
|
+
), # Have to pass this in manually
|
597
591
|
)
|
598
592
|
print()
|
599
593
|
|
600
594
|
if verbosity >= 1:
|
601
|
-
click.
|
595
|
+
click.secho("Running migrations:", fg="cyan")
|
602
596
|
|
603
597
|
post_migrate_state = executor.migrate(
|
604
598
|
targets,
|
plain/models/constraints.py
CHANGED
@@ -347,7 +347,7 @@ class UniqueConstraint(BaseConstraint):
|
|
347
347
|
return path, self.expressions, kwargs
|
348
348
|
|
349
349
|
def validate(self, model, instance, exclude=None):
|
350
|
-
queryset = model.
|
350
|
+
queryset = model.query
|
351
351
|
if self.fields:
|
352
352
|
lookup_kwargs = {}
|
353
353
|
for field_name in self.fields:
|
plain/models/deletion.py
CHANGED
@@ -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.
|
355
|
+
return related_model._meta.base_queryset.filter(predicate)
|
356
356
|
|
357
357
|
def sort(self):
|
358
358
|
sorted_models = []
|
plain/models/fields/__init__.py
CHANGED
@@ -901,7 +901,7 @@ class Field(RegisterLookupMixin):
|
|
901
901
|
if hasattr(self.remote_field, "get_related_field")
|
902
902
|
else "id"
|
903
903
|
)
|
904
|
-
qs = rel_model.
|
904
|
+
qs = rel_model.query.complex_filter(limit_choices_to)
|
905
905
|
if ordering:
|
906
906
|
qs = qs.order_by(*ordering)
|
907
907
|
return (blank_choice if include_blank else []) + [
|
plain/models/fields/related.py
CHANGED
@@ -310,9 +310,6 @@ class RelatedField(FieldCacheMixin, Field):
|
|
310
310
|
|
311
311
|
if self.remote_field.related_name:
|
312
312
|
related_name = self.remote_field.related_name
|
313
|
-
else:
|
314
|
-
related_name = self.opts.default_related_name
|
315
|
-
if related_name:
|
316
313
|
related_name %= {
|
317
314
|
"class": cls.__name__.lower(),
|
318
315
|
"model_name": cls._meta.model_name.lower(),
|
@@ -672,7 +669,7 @@ class ForeignKey(RelatedField):
|
|
672
669
|
if value is None:
|
673
670
|
return
|
674
671
|
|
675
|
-
qs = self.remote_field.model.
|
672
|
+
qs = self.remote_field.model._meta.base_queryset.filter(
|
676
673
|
**{self.remote_field.field_name: value}
|
677
674
|
)
|
678
675
|
qs = qs.complex_filter(self.get_limit_choices_to())
|