Source code for queryable_properties.properties.inheritance

# -*- coding: utf-8 -*-
from copy import deepcopy
from inspect import isclass

import six
from django.db.models import CharField

from ..compat import get_model
from ..utils.internal import QueryPath
from .base import QueryableProperty, QueryablePropertyReference
from .mixins import IgnoreCacheMixin, InheritanceMixin
from .operations import SelectRelatedOperation


[docs] class InheritanceModelProperty(InheritanceMixin, QueryableProperty): """ A property that returns information about the actual model class of objects in inheritance scenarios. """
[docs] def __init__(self, value_generator, output_field, **kwargs): """ Initialize a new property that returns information about the actual model class of objects in inheritance scenarios. :param value_generator: A callable that returns the actual value to be represented for a given model class. Must take a model class as its first argument. :type value_generator: function :param output_field: The output field to use for this property, which must be able to hold the values generated by the given value generator. :type output_field: django.db.models.Field :keyword depth: The maximum depth of the inheritance hierarchy to follow. Instances of model classes below this maximum depth will be treated as objects of the maximum depth. If not provided, no maximum depth will be enforced. """ self.value_generator = value_generator self._inheritance_output_field = output_field super(InheritanceModelProperty, self).__init__(**kwargs)
def _get_value_for_model(self, model): return self.value_generator(model) def get_annotation(self, cls): return self._build_case_expression(cls)
[docs] class InheritanceObjectProperty(IgnoreCacheMixin, InheritanceMixin, QueryableProperty): """ A property that returns the final submodel instance in inheritance scenarios. """ _inheritance_output_field = CharField()
[docs] def __init__(self, **kwargs): """ Initialize a new property that returns the final submodel instance in inheritance scenarios. :keyword depth: The maximum depth of the inheritance hierarchy to follow. Instances of model classes below this maximum depth will be treated as objects of the maximum depth. If not provided, no maximum depth will be enforced. """ super(InheritanceObjectProperty, self).__init__(**kwargs)
def _get_value_for_model(self, model): return '.'.join((model._meta.app_label, model._meta.object_name)) def _resolve(self, model=None, relation_path=QueryPath(), remaining_path=QueryPath()): return InheritanceObjectPropertyReference(self, model or self.model, relation_path), remaining_path def get_value(self, obj): if self._descriptor.has_cached_value(obj): cached_value = self._descriptor.get_cached_value(obj) if isinstance(cached_value, self.model): # The cached value is already the final model object, so it can # be returned as-is. return cached_value # The cached value is a string in '<app_label>.<ModelName>' format # while child relations should have been fetched via # select_related. Determine the actual model class and determine # the path to follow to get the actual child instance. child_obj = deepcopy(obj) model = get_model(*cached_value.split('.', 1)) for part in self._get_child_paths(obj.__class__).get(model, QueryPath()): child_obj = getattr(child_obj, part) else: # No cached value. Perform a query utilizing this property and use # the value from its final result. This allows to re-use the query- # level implementation including the select_related setup. queryset = self.get_queryset_for_object(obj).select_properties(self.name) child_obj = getattr(queryset.get(), self.name) if self.cached or self._descriptor.has_cached_value(obj): self._descriptor.set_cached_value(obj, child_obj) return child_obj def get_filter(self, cls, lookup, value): filter_value = value if isinstance(value, self.model) or (isclass(value) and issubclass(value, self.model)): filter_value = self._get_value_for_model(value) condition = super(InheritanceObjectProperty, self).get_filter(cls, lookup, filter_value) if isinstance(value, self.model): condition &= (QueryPath('pk') + lookup).build_filter(value.pk) return condition def get_annotation(self, cls): return self._build_case_expression(cls)
class InheritanceObjectPropertyReference(QueryablePropertyReference): """ A specialized property reference that allows :class:`InheritanceObjectProperty` objects to be annotated properly. """ __slots__ = () def annotate_query(self, query, full_group_by, select=False, remaining_path=QueryPath()): if select: fields = (child_path.as_str() for child_path in six.itervalues(self.property._get_child_paths(self.model)) if self.property.depth is None or len(child_path) <= self.property.depth) query._lazy_queryable_property_operations.append(SelectRelatedOperation(*fields)) return super(InheritanceObjectPropertyReference, self).annotate_query(query, full_group_by, select, remaining_path)