1"""
  2Reverse relation descriptors for explicit reverse relation declarations.
  3
  4This module contains descriptors for the reverse side of ForeignKeyField and
  5ManyToManyField relations, allowing explicit declaration of reverse accessors
  6without relying on automatic related_name generation.
  7"""
  8
  9from __future__ import annotations
 10
 11from typing import TYPE_CHECKING, Any, Generic, TypeVar
 12
 13if TYPE_CHECKING:
 14    from plain.postgres import Model
 15    from plain.postgres.fields.related_managers import BaseRelatedManager
 16    from plain.postgres.query import QuerySet
 17
 18T = TypeVar("T", bound="Model")
 19# Default to QuerySet[Any] so users can omit the second type parameter
 20QS = TypeVar("QS", bound="QuerySet[Any]", default="QuerySet[Any]")
 21
 22
 23class BaseReverseDescriptor(Generic[T, QS]):
 24    """
 25    Base class for reverse relation descriptors.
 26
 27    Provides common functionality for ReverseForeignKey and ReverseManyToMany
 28    descriptors, including field resolution, validation, and the descriptor protocol.
 29    """
 30
 31    def __init__(self, to: str | type[T], field: str):
 32        self.to = to
 33        self.field_name = field
 34        self.name: str | None = None
 35        self.model: type[Model] | None = None
 36        self._resolved_model: type[T] | None = None
 37        self._resolved_field: Any = None
 38
 39    def contribute_to_class(self, cls: type[Model], name: str) -> None:
 40        """
 41        Register this reverse relation with the model class.
 42
 43        Called by the model metaclass when the model is created.
 44        """
 45        self.name = name
 46        self.model = cls
 47
 48        # Set the descriptor on the class
 49        setattr(cls, name, self)
 50
 51        # Register this as a related object for prefetch support
 52        # We'll do this lazily when the target model is resolved
 53        from plain.postgres.fields.related import lazy_related_operation
 54
 55        def resolve_related_field(
 56            parent_model: type[Model], related_model: type[T]
 57        ) -> None:
 58            """Resolve the target model and field, then register."""
 59            self._resolved_model = related_model
 60            try:
 61                self._resolved_field = related_model._model_meta.get_field(
 62                    self.field_name
 63                )
 64            except Exception as e:
 65                raise ValueError(
 66                    f"Field '{self.field_name}' not found on model "
 67                    f"'{related_model.__name__}' for {self._get_descriptor_type()} '{self.name}' "
 68                    f"on '{cls.__name__}'. Error: {e}"
 69                )
 70
 71            # Validate that the field is the correct type
 72            self._validate_field_type(related_model)
 73
 74        # Use lazy operation to handle circular dependencies
 75        lazy_related_operation(resolve_related_field, cls, self.to)
 76
 77    def __get__(
 78        self, instance: Model | None, owner: type[Model]
 79    ) -> BaseReverseDescriptor[T, QS] | BaseRelatedManager[T, QS]:
 80        """
 81        Get the related manager when accessed on an instance.
 82
 83        When accessed on the class, returns the descriptor.
 84        When accessed on an instance, returns a manager.
 85        """
 86        if instance is None:
 87            return self
 88
 89        # Ensure the related model and field are resolved
 90        if self._resolved_field is None or self.model is None:
 91            model_name = self.model.__name__ if self.model else "Unknown"
 92            raise ValueError(
 93                f"{self._get_descriptor_type()} '{self.name}' on '{model_name}' "
 94                f"has not been resolved yet. The target model may not be registered."
 95            )
 96
 97        # _resolved_model is set alongside _resolved_field in resolve_related_field
 98        assert self._resolved_model is not None, "Model should be resolved with field"
 99
100        # Return a manager bound to this instance
101        return self._create_manager(instance)
102
103    def __set__(self, instance: Model, value: Any) -> None:
104        """Prevent direct assignment to reverse relations."""
105        raise TypeError(
106            f"Direct assignment to the reverse side of a {self._get_field_type()} "
107            f"('{self.name}') is prohibited. Use {self.name}.set() instead."
108        )
109
110    def _get_descriptor_type(self) -> str:
111        """Return the name of this descriptor type for error messages."""
112        raise NotImplementedError("Subclasses must implement _get_descriptor_type()")
113
114    def _get_field_type(self) -> str:
115        """Return the name of the forward field type for error messages."""
116        raise NotImplementedError("Subclasses must implement _get_field_type()")
117
118    def _validate_field_type(self, related_model: type[Model]) -> None:
119        """Validate that the resolved field is the correct type."""
120        raise NotImplementedError("Subclasses must implement _validate_field_type()")
121
122    def _create_manager(self, instance: Model) -> Any:
123        """Create and return the appropriate manager for this instance."""
124        raise NotImplementedError("Subclasses must implement _create_manager()")
125
126
127class ReverseForeignKey(BaseReverseDescriptor[T, QS]):
128    """
129    Descriptor for the reverse side of a ForeignKeyField relation.
130
131    Provides access to the related instances on the "one" side of a one-to-many
132    relationship.
133
134    Example:
135        class Parent(Model):
136            # Basic usage (uses default QuerySet[Child])
137            children: ReverseForeignKey[Child, QuerySet[Child]] = ReverseForeignKey(to="Child", field="parent")
138
139            # With custom QuerySet
140            children: ReverseForeignKey[Child, ChildQuerySet] = ReverseForeignKey(to="Child", field="parent")
141
142        class Child(Model):
143            parent: Parent = ForeignKeyField(Parent, on_delete=models.CASCADE)
144
145    Args:
146        to: The related model (string name or model class)
147        field: The field name on the related model that points back to this model
148    """
149
150    def _get_descriptor_type(self) -> str:
151        return "ReverseForeignKey"
152
153    def _get_field_type(self) -> str:
154        return "ForeignKey"
155
156    def _validate_field_type(self, related_model: type[Model]) -> None:
157        """Validate that the field is a ForeignKey."""
158        from plain.postgres.fields.related import ForeignKeyField
159
160        if not isinstance(self._resolved_field, ForeignKeyField):
161            raise ValueError(
162                f"Field '{self.field_name}' on '{related_model.__name__}' is not a "
163                f"ForeignKey. ReverseForeignKey requires a ForeignKeyField field."
164            )
165
166    def _create_manager(self, instance: Model) -> Any:
167        """Create a ReverseForeignKeyManager for this instance."""
168        from plain.postgres.fields.related_managers import ReverseForeignKeyManager
169
170        assert self._resolved_model is not None
171        return ReverseForeignKeyManager(
172            instance=instance,
173            field=self._resolved_field,
174            related_model=self._resolved_model,
175        )
176
177
178class ReverseManyToMany(BaseReverseDescriptor[T, QS]):
179    """
180    Descriptor for the reverse side of a ManyToManyField relation.
181
182    Provides access to the related instances on the reverse side of a many-to-many
183    relationship.
184
185    Example:
186        class Feature(Model):
187            # Basic usage (uses default QuerySet[Car])
188            cars: ReverseManyToMany[Car, QuerySet[Car]] = ReverseManyToMany(to="Car", field="features")
189
190            # With custom QuerySet
191            cars: ReverseManyToMany[Car, CarQuerySet] = ReverseManyToMany(to="Car", field="features")
192
193        class Car(Model):
194            features: ManyToManyField[Feature] = ManyToManyField(Feature, through=CarFeature)
195
196    Args:
197        to: The related model (string name or model class)
198        field: The field name on the related model that points to this model
199    """
200
201    def _get_descriptor_type(self) -> str:
202        return "ReverseManyToMany"
203
204    def _get_field_type(self) -> str:
205        return "ManyToManyField"
206
207    def _validate_field_type(self, related_model: type[Model]) -> None:
208        """Validate that the field is a ManyToManyField."""
209        from plain.postgres.fields.related import ManyToManyField
210
211        if not isinstance(self._resolved_field, ManyToManyField):
212            raise ValueError(
213                f"Field '{self.field_name}' on '{related_model.__name__}' is not a "
214                f"ManyToManyField. ReverseManyToMany requires a ManyToManyField."
215            )
216
217    def _create_manager(self, instance: Model) -> Any:
218        """Create a ManyToManyManager for this instance."""
219        from plain.postgres.fields.related_managers import ManyToManyManager
220
221        assert self._resolved_model is not None
222        return ManyToManyManager(
223            instance=instance,
224            field=self._resolved_field,
225            through=self._resolved_field.remote_field.through,
226            related_model=self._resolved_model,
227            is_reverse=True,
228            symmetrical=False,
229        )