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
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)
==================

View File

@@ -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:

View File

@@ -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
),

View File

@@ -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

View File

@@ -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.
"""

View File

@@ -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)