plain.models 0.42.0__py3-none-any.whl → 0.43.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.43.0](https://github.com/dropseed/plain/releases/plain-models@0.43.0) (2025-09-12)
4
+
5
+ ### What's changed
6
+
7
+ - The `related_name` parameter is now required for ForeignKey and ManyToManyField relationships if you want a reverse accessor. The `"+"` suffix to disable reverse relations has been removed, and automatic `_set` suffixes are no longer generated ([89fa03979f](https://github.com/dropseed/plain/commit/89fa03979f))
8
+ - Refactored related descriptors and managers for better internal organization and type safety ([9f0b03957a](https://github.com/dropseed/plain/commit/9f0b03957a))
9
+ - Added docstrings and return type annotations to model `query` property and related manager methods for improved developer experience ([544d85b60b](https://github.com/dropseed/plain/commit/544d85b60b))
10
+
11
+ ### Upgrade instructions
12
+
13
+ - Remove any `related_name="+"` usage - if you don't want a reverse accessor, simply omit the `related_name` parameter entirely
14
+ - Update any code that relied on automatic `_set` suffixes - these are no longer generated, so you must use explicit `related_name` values
15
+ - Add explicit `related_name` arguments to all ForeignKey and ManyToManyField definitions where you want reverse access (e.g., `models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles")`)
16
+ - Consider removing `related_name` arguments that are not used in practice
17
+
3
18
  ## [0.42.0](https://github.com/dropseed/plain/releases/plain-models@0.42.0) (2025-09-12)
4
19
 
5
20
  ### What's changed
plain/models/base.py CHANGED
@@ -25,7 +25,7 @@ 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
27
  from plain.models.options import Options
28
- from plain.models.query import F, Q
28
+ from plain.models.query import F, Q, QuerySet
29
29
  from plain.packages import packages_registry
30
30
  from plain.utils.encoding import force_str
31
31
  from plain.utils.hashable import make_hashable
@@ -168,7 +168,8 @@ class ModelBase(type):
168
168
  index.set_name_with_model(cls)
169
169
 
170
170
  @property
171
- def query(cls):
171
+ def query(cls) -> QuerySet:
172
+ """Create a new QuerySet for this model."""
172
173
  return cls._meta.queryset
173
174
 
174
175
 
@@ -12,8 +12,9 @@ from . import Field
12
12
  from .mixins import FieldCacheMixin
13
13
  from .related_descriptors import (
14
14
  ForeignKeyDeferredAttribute,
15
+ ForwardManyToManyDescriptor,
15
16
  ForwardManyToOneDescriptor,
16
- ManyToManyDescriptor,
17
+ ReverseManyToManyDescriptor,
17
18
  ReverseManyToOneDescriptor,
18
19
  )
19
20
  from .related_lookups import (
@@ -123,14 +124,11 @@ class RelatedField(FieldCacheMixin, Field):
123
124
  is_valid_id = (
124
125
  not keyword.iskeyword(related_name) and related_name.isidentifier()
125
126
  )
126
- if not (is_valid_id or related_name.endswith("+")):
127
+ if not is_valid_id:
127
128
  return [
128
129
  preflight.Error(
129
130
  f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model._meta.object_name}.{self.name}",
130
- hint=(
131
- "Related name must be a valid Python identifier or end with a "
132
- "'+'"
133
- ),
131
+ hint="Related name must be a valid Python identifier.",
134
132
  obj=self,
135
133
  id="fields.E306",
136
134
  )
@@ -1236,26 +1234,6 @@ class ManyToManyField(RelatedField):
1236
1234
  return getattr(self, cache_attr)
1237
1235
 
1238
1236
  def contribute_to_class(self, cls, name, **kwargs):
1239
- # To support multiple relations to self, it's useful to have a non-None
1240
- # related name on symmetrical relations for internal reasons. The
1241
- # concept doesn't make a lot of sense externally ("you want me to
1242
- # specify *what* on my non-reversible relation?!"), so we set it up
1243
- # automatically. The funky name reduces the chance of an accidental
1244
- # clash.
1245
- if self.remote_field.symmetrical and (
1246
- self.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT
1247
- or self.remote_field.model == cls._meta.object_name
1248
- ):
1249
- self.remote_field.related_name = f"{name}_rel_+"
1250
- elif self.remote_field.is_hidden():
1251
- # If the backwards relation is disabled, replace the original
1252
- # related_name with one generated from the m2m field name. Plain
1253
- # still uses backwards relations internally and we need to avoid
1254
- # clashes between multiple m2m fields with related_name == '+'.
1255
- self.remote_field.related_name = (
1256
- f"_{cls._meta.package_label}_{cls.__name__.lower()}_{name}_+"
1257
- )
1258
-
1259
1237
  super().contribute_to_class(cls, name, **kwargs)
1260
1238
 
1261
1239
  def resolve_through_model(_, model, field):
@@ -1266,7 +1244,7 @@ class ManyToManyField(RelatedField):
1266
1244
  )
1267
1245
 
1268
1246
  # Add the descriptor for the m2m relation.
1269
- setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
1247
+ setattr(cls, self.name, ForwardManyToManyDescriptor(self.remote_field))
1270
1248
 
1271
1249
  # Set up the accessor for the m2m table name for the relation.
1272
1250
  self.m2m_db_table = self._get_m2m_db_table
@@ -1278,7 +1256,7 @@ class ManyToManyField(RelatedField):
1278
1256
  setattr(
1279
1257
  cls,
1280
1258
  related.get_accessor_name(),
1281
- ManyToManyDescriptor(self.remote_field, reverse=True),
1259
+ ReverseManyToManyDescriptor(self.remote_field),
1282
1260
  )
1283
1261
 
1284
1262
  # Set up the accessors for the column names on the m2m table.