Ticket #3990: admin_readonly_fields_v1.1.diff

File admin_readonly_fields_v1.1.diff, 7.5 KB (added by philippe.raoult@…, 8 years ago)
  • django/contrib/admin/options.py

     
    11from django import oldforms, template
    22from django import newforms as forms
    33from django.contrib.admin import widgets
     4from django.newforms.widgets import DisplayWidget
    45from django.core.exceptions import ImproperlyConfigured, PermissionDenied
    56from django.db import models
    67from django.http import Http404, HttpResponse, HttpResponseRedirect
     
    111112    fields = None
    112113    raw_id_fields = ()
    113114    prepopulated_fields = {}
     115    # this list contains fields that will only be changed when adding an object. They'll only be displayed in change forms
     116    readonly_fields = ()
    114117    filter_vertical = ()
    115118    filter_horizontal = ()
    116119
     
    201204        for fs in self.fieldsets(request):
    202205            yield fs
    203206
    204     def formfield_for_dbfield(self, db_field, **kwargs):
     207    def formfield_for_dbfield_add(self, db_field, override_readonly=True, **kwargs):
    205208        """
     209        call formfield_for_dbfield with override_readonly = True. Used for admin add object forms
     210        """
     211        return self.formfield_for_dbfield(db_field, override_readonly, **kwargs)
     212       
     213    def formfield_for_dbfield(self, db_field, override_readonly=False, **kwargs):
     214        """
    206215        Hook for specifying the form Field instance for a given database Field
    207216        instance.
    208217
    209218        If kwargs are given, they're passed to the form Field's constructor.
    210219        """
     220        # specify that this value cannot be edited by the user. The formfield will decide how to render this.
     221        # The override is used by the create form to set the value.
     222        this_field_is_readonly = False
     223        if not override_readonly and db_field.name in self.readonly_fields:
     224            this_field_is_readonly = True
     225            kwargs['widget'] = DisplayWidget
     226           
    211227        # For ManyToManyFields with a filter interface, use a special Widget.
    212228        if isinstance(db_field, models.ManyToManyField) and db_field.name in (self.filter_vertical + self.filter_horizontal):
    213229            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
    214230            return db_field.formfield(**kwargs)
    215231
    216232        # For DateTimeFields, use a special field and widget.
    217         if isinstance(db_field, models.DateTimeField):
     233        if isinstance(db_field, models.DateTimeField) and not this_field_is_readonly:
    218234            return forms.SplitDateTimeField(required=not db_field.blank,
    219235                widget=widgets.AdminSplitDateTime(), label=capfirst(db_field.verbose_name),
    220236                help_text=db_field.help_text, **kwargs)
    221237
    222238        # For DateFields, add a custom CSS class.
    223         if isinstance(db_field, models.DateField):
     239        if isinstance(db_field, models.DateField) and not this_field_is_readonly:
    224240            kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'})
    225241            return db_field.formfield(**kwargs)
    226242
    227243        # For TimeFields, add a custom CSS class.
    228         if isinstance(db_field, models.TimeField):
     244        if isinstance(db_field, models.TimeField) and not this_field_is_readonly:
    229245            kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})
    230246            return db_field.formfield(**kwargs)
    231247
    232248        # For ForeignKey or ManyToManyFields, use a special widget.
    233249        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
    234             if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
     250            if this_field_is_readonly:
     251                pass # the displaywidget will take care of it !
     252            elif isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
    235253                kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
    236254                return db_field.formfield(**kwargs)
    237255            else:
     
    392410            # Object list will give 'Permission Denied', so go back to admin home
    393411            post_url = '../../../'
    394412
    395         ModelForm = forms.form_for_model(model, formfield_callback=self.formfield_for_dbfield)
     413        ModelForm = forms.form_for_model(model, formfield_callback=self.formfield_for_dbfield_add)
    396414
    397415        if request.POST:
    398416            new_data = request.POST.copy()
     
    443461            new_data = request.POST.copy()
    444462            if opts.has_field_type(models.FileField):
    445463                new_data.update(request.FILES)
     464            # get the values for readonly fields from the object, because they are displayed as strings instead of widgets and thus are not part of the POST request
     465            for fieldname in self.readonly_fields:
     466                fieldvalue = getattr(obj, fieldname)
     467                new_data[fieldname] = fieldvalue
     468               
    446469            form = ModelForm(new_data)
    447470
    448471            if form.is_valid():
     
    507530        "The 'delete' admin view for this model."
    508531        from django.contrib.contenttypes.models import ContentType
    509532        from django.contrib.admin.models import LogEntry, DELETION
     533        from django.contrib.admin.views.main import _get_deleted_objects
    510534        opts = self.model._meta
    511535        app_label = opts.app_label
    512536
  • django/contrib/admin/filterspecs.py

     
    158158    def __init__(self, f, request, params, model):
    159159        super(AllValuesFilterSpec, self).__init__(f, request, params, model)
    160160        self.lookup_val = request.GET.get(f.name, None)
    161         self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name)
     161        self.lookup_choices = model._default_manager.distinct().order_by(f.name).values(f.name)
    162162
    163163    def title(self):
    164164        return self.field.verbose_name
  • django/newforms/widgets.py

     
    33"""
    44
    55__all__ = (
    6     'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
     6    'Widget', 'DisplayWidget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
    77    'FileInput', 'Textarea', 'CheckboxInput',
    88    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
    99    'MultiWidget', 'SplitDateTimeWidget',
     
    6666        return id_
    6767    id_for_label = classmethod(id_for_label)
    6868
     69class DisplayWidget(Widget):
     70    """
     71    Base class for all read-only pseudo-widgets. Those are used by the admin interface to display read-only values
     72    """
     73    def __init__(self, choices=None, attrs=None):       
     74        self.attrs = attrs or {}
     75        self.choices = choices
     76   
     77    def render(self, name, value, attrs=None):
     78        # lookup choices values
     79        if self.choices is not None:
     80            for (primary_key, name) in self.choices:
     81                if value == primary_key:
     82                    value = name
     83                    break
     84        # and replace special values by strings to improve readability
     85        if value is None:
     86            value = '[Null]'
     87        if value == '':
     88            value = '[Blank]'
     89       
     90        return smart_unicode(value)
     91   
     92
    6993class Input(Widget):
    7094    """
    7195    Base class for all <input> widgets (except type='checkbox' and
Back to Top