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 <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2018-12-14 01:43:51 -04:00
parent 68995adb7f
commit 1efec6bd41
6 changed files with 59 additions and 26 deletions

View File

@@ -175,6 +175,8 @@
- Upgraded from Celery 3.1.26 to 4.1.1. The following settings have been - Upgraded from Celery 3.1.26 to 4.1.1. The following settings have been
renamed: CELERY_ALWAYS_EAGER to CELERY_TASK_ALWAYS_EAGER, renamed: CELERY_ALWAYS_EAGER to CELERY_TASK_ALWAYS_EAGER,
BROKER_URL to CELERY_BROKER_URL. 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) 3.1.9 (2018-11-01)
================== ==================

View File

@@ -9,7 +9,7 @@ from django.db.models import Q
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _ 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 import Permission
from mayan.apps.permissions.models import StoredPermission from mayan.apps.permissions.models import StoredPermission
@@ -46,7 +46,7 @@ class AccessControlListManager(models.Manager):
stored_permissions = (permissions.stored_permission,) stored_permissions = (permissions.stored_permission,)
if related: if related:
obj = return_attrib(obj, related) obj = resolve_attribute(obj, related)
try: try:
parent_accessor = ModelPermission.get_inheritance( parent_accessor = ModelPermission.get_inheritance(
@@ -199,7 +199,7 @@ class AccessControlListManager(models.Manager):
return StoredPermission.objects.none() return StoredPermission.objects.none()
else: else:
try: try:
parent_object = return_attrib( parent_object = resolve_attribute(
obj=instance, attrib=parent_accessor obj=instance, attrib=parent_accessor
) )
except AttributeError: except AttributeError:

View File

@@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from .classes import Package from .classes import Package
from .models import UserLocaleProfile from .models import UserLocaleProfile
from .utils import return_attrib from .utils import resolve_attribute
from .widgets import DisableableSelectWidget, PlainWidget, TextAreaDiv from .widgets import DisableableSelectWidget, PlainWidget, TextAreaDiv
@@ -43,7 +43,7 @@ class DetailForm(forms.ModelForm):
super(DetailForm, self).__init__(*args, **kwargs) super(DetailForm, self).__init__(*args, **kwargs)
for extra_field in self.extra_fields: 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 label = 'label' in extra_field and extra_field['label'] or None
# TODO: Add others result types <=> Field types # TODO: Add others result types <=> Field types
if isinstance(result, models.query.QuerySet): if isinstance(result, models.query.QuerySet):
@@ -53,7 +53,7 @@ class DetailForm(forms.ModelForm):
else: else:
self.fields[extra_field['field']] = forms.CharField( self.fields[extra_field['field']] = forms.CharField(
label=extra_field['label'], label=extra_field['label'],
initial=return_attrib( initial=resolve_attribute(
self.instance, self.instance,
extra_field['field'], None extra_field['field'], None
), ),

View File

@@ -10,7 +10,7 @@ import mayan
from ..classes import Collection, Dashboard from ..classes import Collection, Dashboard
from ..literals import MESSAGE_SQLITE_WARNING from ..literals import MESSAGE_SQLITE_WARNING
from ..utils import check_for_sqlite, return_attrib from ..utils import check_for_sqlite, resolve_attribute
register = Library() register = Library()
@@ -30,7 +30,7 @@ def get_collections():
def get_encoded_parameter(item, parameters_dict): def get_encoded_parameter(item, parameters_dict):
result = {} result = {}
for attrib_name, attrib in parameters_dict.items(): 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) return dumps(result)
@@ -41,7 +41,7 @@ def get_type(value):
@register.filter @register.filter
def object_property(value, arg): def object_property(value, arg):
return return_attrib(value, arg) return resolve_attribute(value, arg)
@register.simple_tag @register.simple_tag

View File

@@ -7,6 +7,7 @@ import tempfile
import types import types
from django.conf import settings from django.conf import settings
from django.db.models.constants import LOOKUP_SEP
from django.urls import resolve as django_resolve from django.urls import resolve as django_resolve
from django.urls.base import get_script_prefix from django.urls.base import get_script_prefix
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@@ -129,27 +130,34 @@ def resolve(path, urlconf=None):
return django_resolve(path=path, urlconf=urlconf) 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): if isinstance(attrib, types.FunctionType):
return attrib(obj) return attrib(obj)
elif isinstance( elif isinstance(obj, dict_type) or isinstance(obj, dictionary_type):
obj, dict_type
) or isinstance(obj, dictionary_type):
return obj[attrib] return obj[attrib]
else: else:
result = reduce_function(getattr, attrib.split('.'), obj) try:
if isinstance(result, types.MethodType): result = reduce_function(getattr, attrib.split('.'), obj)
if arguments: if isinstance(result, types.MethodType):
return result(**arguments) if arguments:
return result(**arguments)
else:
return result()
else: else:
return result() return result
else: except AttributeError:
return result 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): 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 meant for related models. Support multiple levels of relationship
using double underscore. using double underscore.
""" """

View File

@@ -8,14 +8,15 @@ from furl import furl
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.admin.utils import label_for_field 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.shortcuts import resolve_url
from django.template import VariableDoesNotExist, Variable from django.template import VariableDoesNotExist, Variable
from django.template.defaulttags import URLNode from django.template.defaulttags import URLNode
from django.urls import Resolver404, resolve from django.urls import Resolver404, resolve
from django.utils.encoding import force_str, force_text 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 from mayan.apps.permissions import Permission
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -469,6 +470,26 @@ class Separator(Link):
class SourceColumn(object): class SourceColumn(object):
_registry = {} _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 @staticmethod
def sort(columns): def sort(columns):
return sorted(columns, key=lambda x: x.order) return sorted(columns, key=lambda x: x.order)
@@ -507,11 +528,13 @@ class SourceColumn(object):
@property @property
def label(self): def label(self):
# TODO: Add support for related fields (dotted or double underscore attributes)
if not self._label: if not self._label:
if self.attribute: if self.attribute:
name, model = SourceColumn.get_attribute_recursive(
attribute=self.attribute, model=self.source._meta.model
)
self._label = label_for_field( self._label = label_for_field(
name=self.attribute, model=self.source._meta.model name=name, model=model
) )
else: else:
self._label = 'Function' self._label = 'Function'
@@ -520,7 +543,7 @@ class SourceColumn(object):
def resolve(self, context): def resolve(self, context):
if self.attribute: if self.attribute:
result = return_attrib(context['object'], self.attribute) result = resolve_attribute(context['object'], self.attribute)
elif self.func: elif self.func:
result = self.func(context=context) result = self.func(context=context)