Source code for queryable_properties.admin

# -*- coding: utf-8 -*-

from django.contrib.admin import ModelAdmin, StackedInline, TabularInline

from ..compat import ADMIN_QUERYSET_METHOD_NAME, admin_validation
from ..exceptions import QueryablePropertyError
from ..managers import QueryablePropertiesQuerySetMixin
from ..utils.internal import QueryPath
from .checks import QueryablePropertiesChecksMixin
from .filters import QueryablePropertyField

__all__ = [
    'QueryablePropertiesAdmin',
    'QueryablePropertiesAdminMixin',
    'QueryablePropertiesStackedInline',
    'QueryablePropertiesTabularInline',
]


[docs] class QueryablePropertiesAdminMixin(object): """ A mixin for admin classes including inlines that allows to use queryable properties in various admin features. """ list_select_properties = () """A sequence of queryable property names that should be selected.""" def __init__(self, *args, **kwargs): super(QueryablePropertiesAdminMixin, self).__init__(*args, **kwargs) if hasattr(self, 'list_filter') and not hasattr(ModelAdmin, 'get_list_filter'): # pragma: no cover # In very old Django versions, there was no get_list_filter method, # therefore the processed queryable property filters must be stored # directly in the list_filter attribute. self.list_filter = self.process_queryable_property_filters(self.list_filter) @classmethod def validate(cls, model): # pragma: no cover cls._ensure_queryable_property_checks() return super(QueryablePropertiesAdminMixin, cls).validate(model) def check(self, *args, **kwargs): self._ensure_queryable_property_checks(self) return super(QueryablePropertiesAdminMixin, self).check(*args, **kwargs) if getattr(getattr(ModelAdmin, 'check', None), '__self__', None): # pragma: no cover # In old Django versions, check was a classmethod. check = classmethod(check) @classmethod def _ensure_queryable_property_checks(cls, obj=None): """ Make sure that the queryable properties admin check extensions are used to avoid errors due to Django's default validation, which would treat queryable property names as invalid. :param obj: The (optional) model admin instance to ensure the queryable property checks for. If not provided, they are ensured for the current class instead. :type obj: ModelAdmin | None """ obj = obj or cls # Dynamically add a mixin that handles queryable properties into the # admin's checks/validation class. for attr_name in ('checks_class', 'validator_class', 'default_validator_class'): checks_class = getattr(obj, attr_name, None) if checks_class: class_name = 'QueryableProperties' + checks_class.__name__ setattr(obj, attr_name, QueryablePropertiesChecksMixin.mix_with_class(checks_class, class_name)) def get_queryset(self, request): # The base method has different names in different Django versions (see # comment on the constant definition). base_method = getattr(super(QueryablePropertiesAdminMixin, self), ADMIN_QUERYSET_METHOD_NAME) # Make sure to use a queryset with queryable properties features. queryset = QueryablePropertiesQuerySetMixin.apply_to(base_method(request)) # Apply list_select_properties. list_select_properties = self.get_list_select_properties(request) if list_select_properties: queryset = queryset.select_properties(*list_select_properties) return queryset def queryset(self, request): # pragma: no cover # Same as get_queryset, but for very old Django versions. Simply # delegate to get_queryset, which is aware of the different methods in # different versions and therefore calls the correct super methods if # necessary. return self.get_queryset(request)
[docs] def get_list_select_properties(self, request): """ Wrapper around the ``list_select_properties`` attribute that allows to dynamically create the list of queryable property names to select based on the given request. :param django.http.HttpRequest request: The request to the admin. :return: A sequence of queryable property names to select. :rtype: collections.Sequence[str] """ return self.list_select_properties
def get_list_filter(self, request): list_filter = super(QueryablePropertiesAdminMixin, self).get_list_filter(request) return self.process_queryable_property_filters(list_filter)
[docs] def process_queryable_property_filters(self, list_filter): """ Process a sequence of list filters to create a new sequence in which queryable property references are replaced with custom callables that make them compatible with Django's filter workflow. :param collections.Sequence list_filter: The list filter sequence. :return: The processed list filter sequence. :rtype: list """ processed_filters = [] for item in list_filter: if not callable(item): if isinstance(item, (tuple, list)): field_name, filter_class = item else: field_name, filter_class = item, None try: item = QueryablePropertyField(self, QueryPath(field_name)).get_filter_creator(filter_class) except QueryablePropertyError: pass processed_filters.append(item) return processed_filters
[docs] class QueryablePropertiesAdmin(QueryablePropertiesAdminMixin, ModelAdmin): """ Base class for admin classes which allows to use queryable properties in various admin features. Intended to be used in place of Django's regular ``ModelAdmin`` class. """
[docs] class QueryablePropertiesStackedInline(QueryablePropertiesAdminMixin, StackedInline): """ Base class for stacked inline classes which allows to use queryable properties in various admin features. Intended to be used in place of Django's regular ``StackedInline`` class. """
[docs] class QueryablePropertiesTabularInline(QueryablePropertiesAdminMixin, TabularInline): """ Base class for tabular inline classes which allows to use queryable properties in various admin features. Intended to be used in place of Django's regular ``TabularInline`` class. """
# In very old django versions, the admin validation happens in one big function # that cannot really be extended well. Therefore, the Django module will be # monkeypatched in order to allow the queryable properties validation to take # effect. django_validate = getattr(admin_validation, 'validate', None) django_validate_inline = getattr(admin_validation, 'validate_inline', None) if django_validate: # pragma: no cover def validate(cls, model): if issubclass(cls, QueryablePropertiesAdminMixin): cls = QueryablePropertiesChecksMixin()._validate_queryable_properties(cls, model) django_validate(cls, model) admin_validation.validate = validate if django_validate_inline: # pragma: no cover def validate_inline(cls, parent, parent_model): if issubclass(cls, QueryablePropertiesAdminMixin): cls = QueryablePropertiesChecksMixin()._validate_queryable_properties(cls, cls.model) django_validate_inline(cls, parent, parent_model) admin_validation.validate_inline = validate_inline