diff --git a/mayan/apps/common/classes.py b/mayan/apps/common/classes.py index ffd4b9e626..6af4eea5b4 100644 --- a/mayan/apps/common/classes.py +++ b/mayan/apps/common/classes.py @@ -301,7 +301,9 @@ class Template(object): self.__class__._registry[name] = self def get_absolute_url(self): - return reverse('rest_api:template-detail', args=(self.name,)) + return reverse( + viewname='rest_api:template-detail', kwargs={'template_pk': self.name} + ) def render(self, request): context = { @@ -313,8 +315,10 @@ class Template(object): context=context, ).render() - self.html = result.rendered_content - self.hex_hash = hashlib.sha256(result.content).hexdigest() + content = result.rendered_content.replace('\n', '') + + self.html = content + self.hex_hash = hashlib.sha256(content).hexdigest() return self diff --git a/mayan/apps/common/forms.py b/mayan/apps/common/forms.py index d7cba8309e..88e6ee5d56 100644 --- a/mayan/apps/common/forms.py +++ b/mayan/apps/common/forms.py @@ -4,14 +4,17 @@ 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 from django.utils.translation import ugettext_lazy as _ +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 @@ -36,29 +39,94 @@ class ChoiceForm(forms.Form): } ) - selection = forms.MultipleChoiceField(widget=DisableableSelectWidget()) + selection = forms.MultipleChoiceField( + required=False, 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) ) @@ -129,33 +197,48 @@ class FileDisplayForm(forms.Form): self.fields['text'].initial = file_object.read() +class FilteredSelectionFormOptions(FormOptions): + # Dictionary list of option names and default values + option_definitions = ( + {'allow_multiple': False}, + {'field_name': None}, + {'help_text': None}, + {'label': None}, + {'model': None}, + {'permission': None}, + {'queryset': None}, + {'required': True}, + {'user': None}, + {'widget_class': None}, + {'widget_attributes': {'size': '10'}}, + ) + + class FilteredSelectionForm(forms.Form): """ Form to select the from a list of choice filtered by access. Can be configure to allow single or multiple selection. """ def __init__(self, *args, **kwargs): - field_name = kwargs.pop('field_name', None) - label = kwargs.pop('label', None) - help_text = kwargs.pop('help_text', None) - permission = kwargs.pop('permission', None) - queryset = kwargs.pop('queryset', None) + opts = FilteredSelectionFormOptions( + form=self, kwargs=kwargs, options=getattr(self, 'Meta', None) + ) - if queryset is None: - model = kwargs.pop('model', None) - if not model: + if opts.queryset is None: + if not opts.model: raise ImproperlyConfigured( - 'Must provide a queryset or a model.' + '{} requires a queryset or a model to be specified as ' + 'a meta option or passed during initialization.'.format( + self.__class__.__name__ + ) ) - queryset = model.objects.all() + queryset = opts.model.objects.all() + else: + queryset = opts.queryset - user = kwargs.pop('user', None) - widget_class = kwargs.pop('widget_class', None) - widget_attributes = kwargs.pop('widget_attributes', {'size': '10'}) - - if not widget_class: - if kwargs.pop('allow_multiple', False): + if not opts.widget_class: + if opts.allow_multiple: extra_kwargs = {} field_class = forms.ModelMultipleChoiceField widget_class = forms.widgets.SelectMultiple @@ -163,18 +246,22 @@ class FilteredSelectionForm(forms.Form): extra_kwargs = {'empty_label': None} field_class = forms.ModelChoiceField widget_class = forms.widgets.Select + else: + widget_class = opts.widget_class + + if opts.permission: + queryset = AccessControlList.objects.restrict_queryset( + permission=opts.permission, queryset=queryset, + user=opts.user + ) super(FilteredSelectionForm, self).__init__(*args, **kwargs) - if permission: - queryset = AccessControlList.objects.filter_by_access( - permission=permission, queryset=queryset, user=user - ) - - self.fields[field_name] = field_class( - help_text=help_text, label=label, - queryset=queryset, required=True, - widget=widget_class(attrs=widget_attributes), **extra_kwargs + self.fields[opts.field_name] = field_class( + help_text=opts.help_text, label=opts.label, + queryset=queryset, required=opts.required, + widget=widget_class(attrs=opts.widget_attributes), + **extra_kwargs ) diff --git a/mayan/apps/common/migrations/0011_auto_20181229_0738.py b/mayan/apps/common/migrations/0012_auto_20181229_0738.py similarity index 100% rename from mayan/apps/common/migrations/0011_auto_20181229_0738.py rename to mayan/apps/common/migrations/0012_auto_20181229_0738.py