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)