diff --git a/mayan/apps/common/forms.py b/mayan/apps/common/forms.py index 97acc3405f..3310b997e6 100644 --- a/mayan/apps/common/forms.py +++ b/mayan/apps/common/forms.py @@ -4,6 +4,7 @@ import os from django import forms from django.conf import settings +from django.contrib.admin.utils import label_for_field from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils.module_loading import import_string @@ -13,7 +14,7 @@ from mayan.apps.acls.models import AccessControlList from .classes import Package from .models import UserLocaleProfile -from .utils import resolve_attribute +from .utils import introspect_attribute, resolve_attribute from .widgets import DisableableSelectWidget, PlainWidget, TextAreaDiv @@ -41,26 +42,89 @@ class ChoiceForm(forms.Form): selection = forms.MultipleChoiceField(widget=DisableableSelectWidget()) +class FormOptions(object): + def __init__(self, form, kwargs, options=None): + """ + Option definitions will be iterated. The option value will be + determined in the following order: as passed via keyword + arguments during form intialization, as form get_... method or + finally as static Meta options. This is to allow a form with + Meta options or method to be overrided at initialization + and increase the usability of a single class. + """ + for option_definition in self.option_definitions: + name = option_definition.keys()[0] + default_value = option_definition.values()[0] + + try: + # Check for a runtime value via kwargs + value = kwargs.pop(name) + except KeyError: + try: + # Check if there is a get_... method + value = getattr(self, 'get_{}'.format(name))() + except AttributeError: + try: + # Check the meta class options + value = getattr(options, name) + except AttributeError: + value = default_value + + setattr(self, name, value) + + +class DetailFormOption(FormOptions): + # Dictionary list of option names and default values + option_definitions = ( + {'extra_fields': []}, + ) + + class DetailForm(forms.ModelForm): def __init__(self, *args, **kwargs): - self.extra_fields = kwargs.pop('extra_fields', ()) + self.opts = DetailFormOption( + form=self, kwargs=kwargs, options=getattr(self, 'Meta', None) + ) super(DetailForm, self).__init__(*args, **kwargs) - for extra_field in self.extra_fields: - result = resolve_attribute(obj=self.instance, attribute=extra_field['field']) - label = 'label' in extra_field and extra_field['label'] or None + for extra_field in self.opts.extra_fields: + obj = extra_field.get('object', self.instance) + field = extra_field['field'] + + result = resolve_attribute( + attribute=field, obj=obj + ) + + label = extra_field.get('label', None) + + if not label: + attribute_name, obj = introspect_attribute( + attribute_name=field, obj=obj + ) + + if not obj: + label = _('None') + else: + try: + label = getattr( + getattr(obj, attribute_name), 'short_description' + ) + except AttributeError: + label = label_for_field( + name=attribute_name, model=obj + ) + # TODO: Add others result types <=> Field types if isinstance(result, models.query.QuerySet): - self.fields[extra_field['field']] = \ - forms.ModelMultipleChoiceField( - queryset=result, label=label) + self.fields[field] = forms.ModelMultipleChoiceField( + queryset=result, label=label + ) else: - self.fields[extra_field['field']] = forms.CharField( - label=extra_field['label'], + self.fields[field] = forms.CharField( initial=resolve_attribute( - obj=self.instance, - attribute=extra_field['field'] - ), + obj=obj, + attribute=field + ), label=label, widget=extra_field.get('widget', PlainWidget) ) @@ -131,7 +195,7 @@ class FileDisplayForm(forms.Form): self.fields['text'].initial = file_object.read() -class FilteredSelectionFormOptions(object): +class FilteredSelectionFormOptions(FormOptions): # Dictionary list of option names and default values option_definitions = ( {'allow_multiple': False}, @@ -147,35 +211,6 @@ class FilteredSelectionFormOptions(object): {'widget_attributes': {'size': '10'}}, ) - def __init__(self, form, kwargs, options=None): - """ - Option definitions will be iterated. The option value will be - determined in the following order: as passed via keyword - arguments during form intialization, as form get_... method or - finally as static Meta options. This is to allow a form with - Meta options or method to be overrided at initialization - and increase the usability of a single class. - """ - for option_definition in self.option_definitions: - name = option_definition.keys()[0] - default_value = option_definition.values()[0] - - try: - # Check for a runtime value via kwargs - value = kwargs.pop(name) - except KeyError: - try: - # Check if there is a get_... method - value = getattr(self, 'get_{}'.format(name))() - except AttributeError: - try: - # Check the meta class options - value = getattr(options, name) - except AttributeError: - value = default_value - - setattr(self, name, value) - class FilteredSelectionForm(forms.Form): """ diff --git a/mayan/apps/common/utils.py b/mayan/apps/common/utils.py index e5456b5a02..e88f7a2c4e 100644 --- a/mayan/apps/common/utils.py +++ b/mayan/apps/common/utils.py @@ -6,6 +6,7 @@ import shutil import tempfile from django.conf import settings +from django.core.exceptions import FieldDoesNotExist from django.db.models.constants import LOOKUP_SEP from django.urls import resolve as django_resolve from django.urls.base import get_script_prefix @@ -142,6 +143,30 @@ def get_storage_subclass(dotted_path): return StorageSubclass +def introspect_attribute(attribute_name, obj): + try: + # Try as a related field + obj._meta.get_field(field_name=attribute_name) + except (AttributeError, FieldDoesNotExist): + attribute_name = attribute_name.replace('__', '.') + + try: + # If there are separators in the attribute name, traverse them + # to the final attribute + attribute_part, attribute_remaining = attribute_name.split( + '.', 1 + ) + except ValueError: + return attribute_name, obj + else: + return introspect_attribute( + attribute_name=attribute_part, + obj=related_field.related_model, + ) + else: + return attribute_name, obj + + def TemporaryFile(*args, **kwargs): kwargs.update({'dir': setting_temporary_directory.value}) return tempfile.TemporaryFile(*args, **kwargs)