From 1efec6bd41b1f6fc03421a74ced4e36f4a24cd60 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 14 Dec 2018 01:43:51 -0400 Subject: [PATCH] Navigation: Related field support to SourceColumn Add support to the SourceColumn class to resolve related fields using the double underscore as separator. Columns that use related no longer have to use throw away lambdas. Signed-off-by: Roberto Rosario --- HISTORY.rst | 2 ++ mayan/apps/acls/managers.py | 6 ++-- mayan/apps/common/forms.py | 6 ++-- mayan/apps/common/templatetags/common_tags.py | 6 ++-- mayan/apps/common/utils.py | 32 +++++++++++------- mayan/apps/navigation/classes.py | 33 ++++++++++++++++--- 6 files changed, 59 insertions(+), 26 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 26d2f4bd18..bc5096a737 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -175,6 +175,8 @@ - Upgraded from Celery 3.1.26 to 4.1.1. The following settings have been renamed: CELERY_ALWAYS_EAGER to CELERY_TASK_ALWAYS_EAGER, BROKER_URL to CELERY_BROKER_URL. +- Internal change. Add support to the SourceColumn class to resolve + related fields using the double underscore as separator. 3.1.9 (2018-11-01) ================== diff --git a/mayan/apps/acls/managers.py b/mayan/apps/acls/managers.py index 21f7058fac..a4da3a0e85 100644 --- a/mayan/apps/acls/managers.py +++ b/mayan/apps/acls/managers.py @@ -9,7 +9,7 @@ from django.db.models import Q from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ -from mayan.apps.common.utils import return_attrib, return_related +from mayan.apps.common.utils import resolve_attribute, return_related from mayan.apps.permissions import Permission from mayan.apps.permissions.models import StoredPermission @@ -46,7 +46,7 @@ class AccessControlListManager(models.Manager): stored_permissions = (permissions.stored_permission,) if related: - obj = return_attrib(obj, related) + obj = resolve_attribute(obj, related) try: parent_accessor = ModelPermission.get_inheritance( @@ -199,7 +199,7 @@ class AccessControlListManager(models.Manager): return StoredPermission.objects.none() else: try: - parent_object = return_attrib( + parent_object = resolve_attribute( obj=instance, attrib=parent_accessor ) except AttributeError: diff --git a/mayan/apps/common/forms.py b/mayan/apps/common/forms.py index 03f5d7f171..ceb6e61c11 100644 --- a/mayan/apps/common/forms.py +++ b/mayan/apps/common/forms.py @@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _ from .classes import Package from .models import UserLocaleProfile -from .utils import return_attrib +from .utils import resolve_attribute from .widgets import DisableableSelectWidget, PlainWidget, TextAreaDiv @@ -43,7 +43,7 @@ class DetailForm(forms.ModelForm): super(DetailForm, self).__init__(*args, **kwargs) for extra_field in self.extra_fields: - result = return_attrib(self.instance, extra_field['field']) + result = resolve_attribute(self.instance, extra_field['field']) label = 'label' in extra_field and extra_field['label'] or None # TODO: Add others result types <=> Field types if isinstance(result, models.query.QuerySet): @@ -53,7 +53,7 @@ class DetailForm(forms.ModelForm): else: self.fields[extra_field['field']] = forms.CharField( label=extra_field['label'], - initial=return_attrib( + initial=resolve_attribute( self.instance, extra_field['field'], None ), diff --git a/mayan/apps/common/templatetags/common_tags.py b/mayan/apps/common/templatetags/common_tags.py index 87f1cfe215..3e14df762c 100644 --- a/mayan/apps/common/templatetags/common_tags.py +++ b/mayan/apps/common/templatetags/common_tags.py @@ -10,7 +10,7 @@ import mayan from ..classes import Collection, Dashboard from ..literals import MESSAGE_SQLITE_WARNING -from ..utils import check_for_sqlite, return_attrib +from ..utils import check_for_sqlite, resolve_attribute register = Library() @@ -30,7 +30,7 @@ def get_collections(): def get_encoded_parameter(item, parameters_dict): result = {} for attrib_name, attrib in parameters_dict.items(): - result[attrib_name] = return_attrib(item, attrib) + result[attrib_name] = resolve_attribute(item, attrib) return dumps(result) @@ -41,7 +41,7 @@ def get_type(value): @register.filter def object_property(value, arg): - return return_attrib(value, arg) + return resolve_attribute(value, arg) @register.simple_tag diff --git a/mayan/apps/common/utils.py b/mayan/apps/common/utils.py index bd500d4af8..e931b2c34b 100644 --- a/mayan/apps/common/utils.py +++ b/mayan/apps/common/utils.py @@ -7,6 +7,7 @@ import tempfile import types from django.conf import settings +from django.db.models.constants import LOOKUP_SEP from django.urls import resolve as django_resolve from django.urls.base import get_script_prefix from django.utils.datastructures import MultiValueDict @@ -129,27 +130,34 @@ def resolve(path, urlconf=None): return django_resolve(path=path, urlconf=urlconf) -def return_attrib(obj, attrib, arguments=None): +def resolve_attribute(obj, attrib, arguments=None): if isinstance(attrib, types.FunctionType): return attrib(obj) - elif isinstance( - obj, dict_type - ) or isinstance(obj, dictionary_type): + elif isinstance(obj, dict_type) or isinstance(obj, dictionary_type): return obj[attrib] else: - result = reduce_function(getattr, attrib.split('.'), obj) - if isinstance(result, types.MethodType): - if arguments: - return result(**arguments) + try: + result = reduce_function(getattr, attrib.split('.'), obj) + if isinstance(result, types.MethodType): + if arguments: + return result(**arguments) + else: + return result() else: - return result() - else: - return result + return result + except AttributeError: + if LOOKUP_SEP in attrib: + attrib = attrib.replace(LOOKUP_SEP, '.') + return resolve_attribute( + obj=obj, attrib=attrib, arguments=arguments + ) + else: + raise def return_related(instance, related_field): """ - This functions works in a similar method to return_attrib but is + This functions works in a similar method to resolve_attribute but is meant for related models. Support multiple levels of relationship using double underscore. """ diff --git a/mayan/apps/navigation/classes.py b/mayan/apps/navigation/classes.py index 96adafcbec..c68fedec5d 100644 --- a/mayan/apps/navigation/classes.py +++ b/mayan/apps/navigation/classes.py @@ -8,14 +8,15 @@ from furl import furl from django.apps import apps from django.conf import settings from django.contrib.admin.utils import label_for_field -from django.core.exceptions import PermissionDenied +from django.core.exceptions import FieldDoesNotExist, PermissionDenied +from django.db.models.constants import LOOKUP_SEP from django.shortcuts import resolve_url from django.template import VariableDoesNotExist, Variable from django.template.defaulttags import URLNode from django.urls import Resolver404, resolve from django.utils.encoding import force_str, force_text -from mayan.apps.common.utils import return_attrib +from mayan.apps.common.utils import resolve_attribute from mayan.apps.permissions import Permission logger = logging.getLogger(__name__) @@ -469,6 +470,26 @@ class Separator(Link): class SourceColumn(object): _registry = {} + @staticmethod + def get_attribute_recursive(attribute, model): + """ + Walk over the double underscore (__) separated path to the last + field. Returns the field name and the corresponding model class. + Used to introspect the label or short_description of a model's + attribute. + """ + last_model = model + for part in attribute.split(LOOKUP_SEP): + last_model = model + try: + field = model._meta.get_field(part) + except FieldDoesNotExist: + break; + else: + model = field.related_model or field.model + + return part, last_model + @staticmethod def sort(columns): return sorted(columns, key=lambda x: x.order) @@ -507,11 +528,13 @@ class SourceColumn(object): @property def label(self): - # TODO: Add support for related fields (dotted or double underscore attributes) if not self._label: if self.attribute: + name, model = SourceColumn.get_attribute_recursive( + attribute=self.attribute, model=self.source._meta.model + ) self._label = label_for_field( - name=self.attribute, model=self.source._meta.model + name=name, model=model ) else: self._label = 'Function' @@ -520,7 +543,7 @@ class SourceColumn(object): def resolve(self, context): if self.attribute: - result = return_attrib(context['object'], self.attribute) + result = resolve_attribute(context['object'], self.attribute) elif self.func: result = self.func(context=context)