mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			120 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from django.db import models
 | |
| from django.db.models.fields.related import ReverseManyToOneDescriptor
 | |
| from django.db.models.lookups import StartsWith
 | |
| from django.db.models.query_utils import PathInfo
 | |
| 
 | |
| 
 | |
| class CustomForeignObjectRel(models.ForeignObjectRel):
 | |
|     """
 | |
|     Define some extra Field methods so this Rel acts more like a Field, which
 | |
|     lets us use ReverseManyToOneDescriptor in both directions.
 | |
|     """
 | |
| 
 | |
|     @property
 | |
|     def foreign_related_fields(self):
 | |
|         return tuple(lhs_field for lhs_field, rhs_field in self.field.related_fields)
 | |
| 
 | |
|     def get_attname(self):
 | |
|         return self.name
 | |
| 
 | |
| 
 | |
| class StartsWithRelation(models.ForeignObject):
 | |
|     """
 | |
|     A ForeignObject that uses StartsWith operator in its joins instead of
 | |
|     the default equality operator. This is logically a many-to-many relation
 | |
|     and creates a ReverseManyToOneDescriptor in both directions.
 | |
|     """
 | |
| 
 | |
|     auto_created = False
 | |
| 
 | |
|     many_to_many = False
 | |
|     many_to_one = True
 | |
|     one_to_many = False
 | |
|     one_to_one = False
 | |
| 
 | |
|     rel_class = CustomForeignObjectRel
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         kwargs["on_delete"] = models.DO_NOTHING
 | |
|         super().__init__(*args, **kwargs)
 | |
| 
 | |
|     @property
 | |
|     def field(self):
 | |
|         """
 | |
|         Makes ReverseManyToOneDescriptor work in both directions.
 | |
|         """
 | |
|         return self.remote_field
 | |
| 
 | |
|     def get_extra_restriction(self, alias, related_alias):
 | |
|         to_field = self.remote_field.model._meta.get_field(self.to_fields[0])
 | |
|         from_field = self.model._meta.get_field(self.from_fields[0])
 | |
|         return StartsWith(to_field.get_col(alias), from_field.get_col(related_alias))
 | |
| 
 | |
|     def get_joining_fields(self, reverse_join=False):
 | |
|         return ()
 | |
| 
 | |
|     def get_path_info(self, filtered_relation=None):
 | |
|         to_opts = self.remote_field.model._meta
 | |
|         from_opts = self.model._meta
 | |
|         return [
 | |
|             PathInfo(
 | |
|                 from_opts=from_opts,
 | |
|                 to_opts=to_opts,
 | |
|                 target_fields=(to_opts.pk,),
 | |
|                 join_field=self,
 | |
|                 m2m=False,
 | |
|                 direct=False,
 | |
|                 filtered_relation=filtered_relation,
 | |
|             )
 | |
|         ]
 | |
| 
 | |
|     def get_reverse_path_info(self, filtered_relation=None):
 | |
|         to_opts = self.model._meta
 | |
|         from_opts = self.remote_field.model._meta
 | |
|         return [
 | |
|             PathInfo(
 | |
|                 from_opts=from_opts,
 | |
|                 to_opts=to_opts,
 | |
|                 target_fields=(to_opts.pk,),
 | |
|                 join_field=self.remote_field,
 | |
|                 m2m=False,
 | |
|                 direct=False,
 | |
|                 filtered_relation=filtered_relation,
 | |
|             )
 | |
|         ]
 | |
| 
 | |
|     def contribute_to_class(self, cls, name, private_only=False):
 | |
|         super().contribute_to_class(cls, name, private_only)
 | |
|         setattr(cls, self.name, ReverseManyToOneDescriptor(self))
 | |
| 
 | |
| 
 | |
| class BrokenContainsRelation(StartsWithRelation):
 | |
|     """
 | |
|     This model is designed to yield no join conditions and
 | |
|     raise an exception in ``Join.as_sql()``.
 | |
|     """
 | |
| 
 | |
|     def get_extra_restriction(self, alias, related_alias):
 | |
|         return None
 | |
| 
 | |
| 
 | |
| class SlugPage(models.Model):
 | |
|     slug = models.CharField(max_length=20, unique=True)
 | |
|     descendants = StartsWithRelation(
 | |
|         "self",
 | |
|         from_fields=["slug"],
 | |
|         to_fields=["slug"],
 | |
|         related_name="ascendants",
 | |
|     )
 | |
|     containers = BrokenContainsRelation(
 | |
|         "self",
 | |
|         from_fields=["slug"],
 | |
|         to_fields=["slug"],
 | |
|     )
 | |
| 
 | |
|     class Meta:
 | |
|         ordering = ["slug"]
 | |
| 
 | |
|     def __str__(self):
 | |
|         return "SlugPage %s" % self.slug
 |