django-polymorphic Documentation

repository·main·Indexed 23 days ago

https://github.com/django-commons/django-polymorphic

Seamless polymorphic inheritance for Django models. This library ensures that queries on a base model return instances of the actual subclasses rather than just the base class. It includes specialized tools for Django admin integration (PolymorphicParentModelAdmin, PolymorphicChildModelAdmin), polymorphic inlines, and advanced queryset filtering using instance_of and not_instance_of.

Tokens
21.5K
Snippets
45
Records
136
Agent score
82%

What's inside django-polymorphic

  1. Features of django-polymorphic

    main

    django-polymorphic provides several enhancements for working with inherited models:

    ORM Integration

    • Descriptor Support: Works with ForeignKey, ManyToManyField, and OneToOneField.
    • Proxy Models: Supports Django proxy models.
    • Filtering/Ordering: Allows filtering and ordering on inherited model fields using the __ syntax (e.g., ArtProject___artist).
    • Type Filtering: Provides methods to filter by specific model types using instance_of and not_instance_of on the PolymorphicQuerySet.
    • Queryset Composition: Supports combining querysets from different models using the OR operator (qs1 | qs2).
    • Custom Managers: Supports user-defined managers.

    Other Features

    • Admin Integration: Full support for the Django admin interface.
    • Formsets: Built-in support for polymorphic formsets.
    • Performance: Optimized to use the minimum number of queries required to fetch inherited models.
    • Control: Ability to disable polymorphic behavior when necessary.
  2. What is django-polymorphic?

    main

    django-polymorphic is a Django application that enhances standard model inheritance. In vanilla Django, querying a base model returns instances of the base class even if the underlying data belongs to a subclass. django-polymorphic ensures that when you query a base model, the ORM automatically returns the correct subclass instances.

    For example, if you have a Project base model and subclasses like ArtProject and ResearchProject, calling Project.objects.all() will return a list containing ArtProject and ResearchProject instances instead of just generic Project instances.

    # With django-polymorphic:
    >>> Project.objects.all()
    [
        <Project:         id 1, topic "Department Party">
        <ArtProject:      id 2, topic "Painting with Tim", artist "T. Turner">
        <ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter">
    ]
    
    # Without django-polymorphic (vanilla Django):
    >>> Project.objects.all()
    [
        <Project: id 1, topic "Department Party">
        <Project: id 2, topic "Painting with Tim">
        <Project: id 3, topic "Swallow Aerodynamics">
    ]
  3. Handle migrations for polymorphic models

    main

    When updating django-polymorphic, you may notice the generation of new migrations that perform AlterModelOptions (e.g., setting options={}). This is an expected side effect of internal fixes to how model options and managers are handled (specifically related to issue #815).

    Note for version 4.9.0: This update may generate new migrations for your polymorphic models. This is normal and safe to apply.

    migrations.AlterModelOptions(
        name='modelname',
        options={},
    )
  4. Understand django-polymorphic query performance

    main

    Unlike ad-hoc polymorphic solutions that execute one SQL query per object (using get_real_instance()), django-polymorphic executes only one additional SQL query per unique derived class found in the result set.

    When you execute ModelA.objects.filter(...), the total number of queries follows this pattern:

    1. One query to retrieve the base ModelA objects.
    2. One additional query for each unique subclass present in the results.

    Examples:

    • 100 objects, all are ModelA: 1 query.
    • 50 objects are ModelA and 50 are ModelB: 2 queries.
    • 100 objects, each is a different subclass: 101 queries (the pathological worst case).
  5. How polymorphic model deletion works

    main

    Deletion of polymorphic models follows standard Django rules for model inheritance hierarchies. Django walks the inheritance graph to collect affected objects and order SQL statements to respect database constraints and signals.

    To prevent PolymorphicQuerySet and PolymorphicManager from confusing Django's graph walker (by returning concrete subclass instances instead of base class instances during reverse relationship traversal), django-polymorphic automatically wraps ForeignKey.on_delete handlers of reverse relations to polymorphic models with PolymorphicGuard. This disables polymorphic behavior during the deletion collection process.

    Key takeaway: You can define polymorphic models using standard Django on_delete actions. PolymorphicModel handles the wrapping automatically.

  6. Admin behavior change for PolymorphicParentModelAdmin

    main

    In version 4.11.5, a change was made to how the Django admin handles redirects after saving an object.

    If both a root model and an intermediate subclass have a PolymorphicParentModelAdmin registered, the admin will now redirect to the most-derived (nearest) parent admin instead of the root model's admin.

  7. What are Polymorphic Models in django-polymorphic?

    main

    In standard Django, querying a base model returns instances of that base model, even if the underlying database record belongs to a subclass. django-polymorphic changes this behavior so that when you query a base model, the returned queryset contains instances of the actual subclassed models.

    This is useful for handling inheritance hierarchies where different subclasses have unique fields that you want to access automatically when retrieving objects through the base class.

    # Example of polymorphic behavior
    >>> Project.objects.create(topic="Department Party")
    >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
    >>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
    
    # Querying the base model returns the specific subclasses
    >>> Project.objects.all()
    [ <Project:         id 1, topic "Department Party">,
      <ArtProject:      id 2, topic "Painting with Tim", artist "T. Turner">,
      <ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
  8. Use Type Hint Descriptors for polymorphic relationships

    main
    The polymorphic.managers module provides several descriptor classes designed to work with type hints and handle polymorphic relationships (One-to-One, Many-to-One, and Many-to-Many) in both forward and reverse directions. These descriptors help maintain type safety and correct behavior when accessing related polymorphic models.
  9. Delete multiple levels of child rows via upcasting

    main

    You can delete multiple levels of a model inheritance hierarchy by deleting from a specific parent level.

    Example: If you have a hierarchy Base -> ChildA -> ChildB:

    • Deleting a ChildB row from its ChildA parent instance will delete both the ChildA and ChildB rows.
    • This leaves behind a concrete row of type Base.
  10. Inherit managers in polymorphic models

    main

    Polymorphic models propagate all managers from their base models, provided those managers are polymorphic. If a base PolymorphicModel defines managers, any model inheriting from that base will also have access to those same managers and their custom methods.

    For example, if Project defines objects_ordered, a subclass ArtProject will also have ArtProject.objects_ordered available, and calling methods on it will return the correct polymorphic results for the subclass.

    from polymorphic.models import PolymorphicModel
    from polymorphic.managers import PolymorphicManager
    
    class TimeOrderedManager(PolymorphicManager):
        def get_queryset(self):
            qs = super(TimeOrderedManager, self).get_queryset()
            return qs.order_by('-start_date')
    
        def most_recent(self):
            qs = self.get_queryset()
            return qs[:10]
    
    class Project(PolymorphicModel):
        objects = PolymorphicManager()
        objects_ordered = TimeOrderedManager()
        start_date = DateTimeField()
    
    class ArtProject(Project):
        artist = models.CharField(max_length=30)
    
    # ArtProject inherits 'objects' and 'objects_ordered' from Project
    # ArtProject.objects_ordered.most_recent() works as expected.