django admin make a field read-only when modifying obj but required when adding new obj

后端 未结 7 1555
甜味超标
甜味超标 2020-12-04 09:31

In admin I would like to disable a field when modifying object, but make it required when adding new object.

Whats the django way to go about this one?

相关标签:
7条回答
  • 2020-12-04 10:00

    The situation with inline forms is still not fixed for Django 2.2.x but the solution from John is actually pretty smart.

    Code slightly tuned to my situation:

    class NoteListInline(admin.TabularInline):
    """ Notes list, readonly """
        model = Note
        verbose_name = _('Note')
        verbose_name_plural = _('Notes')
        extra = 0
        fields = ('note', 'created_at')
        readonly_fields = ('note', 'created_at')
    
        def has_add_permission(self, request, obj=None):
        """ Only add notes through AddInline """
        return False
    
    class NoteAddInline(admin.StackedInline):
        """ Notes edit field """
        model = Note
        verbose_name = _('Note')
        verbose_name_plural = _('Notes')
        extra = 1
        fields = ('note',)
        can_delete = False
    
        def get_queryset(self, request):
            queryset = super().get_queryset(request)
            return queryset.none()  # no existing records will appear
    
    @admin.register(MyModel)
    class MyModelAdmin(admin.ModelAdmin):
        # ...
        inlines = (NoteListInline, NoteAddInline)
        # ...
    
    0 讨论(0)
  • 2020-12-04 10:02

    FYI: in case someone else runs into the same two problems I encountered:

    1. You should still declare any permanently readonly_fields in the body of the class, as the readonly_fields class attribute will be accessed from validation (see django.contrib.admin.validation: validate_base(), line.213 appx)

    2. This won't work with Inlines as the obj passed to get_readonly_fields() is the parent obj (I have two rather hacky and low-security solutions using css or js)

    0 讨论(0)
  • 2020-12-04 10:05

    Got a similar problem. I solved it with "add_fieldsets" and "restricted_fieldsets" in the ModelAdmin.

    from django.contrib import admin  
    class MyAdmin(admin.ModelAdmin):
     declared_fieldsets = None
     restricted_fieldsets = (
        (None, {'fields': ('mod_obj1', 'mod_obj2')}),
        ( 'Text', {'fields': ('mod_obj3', 'mod_obj4',)}),
     )
    
     add_fieldsets = (
                (None, {
                 'classes': ('wide',),
                 'fields': ('add_obj1', 'add_obj2', )}),
                 )
    

    Please see e.g.: http://code.djangoproject.com/svn/django/trunk/django/contrib/auth/admin.py

    But this doesn't protect your model from later changes of "add_objX". If you want this too, I think you have to go the way over the Model class "save" function and check for changes there.

    See: www.djangoproject.com/documentation/models/save_delete_hooks/

    Greez, Nick

    0 讨论(0)
  • 2020-12-04 10:09

    A variation based on the previous excellent suggestion of Bernhard Vallant, which also preserves any possible customization provided by the base class (if any):

    class MyModelAdmin(BaseModelAdmin):
    
        def get_readonly_fields(self, request, obj=None):
            readonly_fields = super(MyModelAdmin, self).get_readonly_fields(request, obj)
            if obj: # editing an existing object
                return readonly_fields + ['field1', ..]
            return readonly_fields
    
    0 讨论(0)
  • 2020-12-04 10:10

    You can do this by overriding the formfield_for_foreignkey method of the ModelAdmin:

    from django import forms
    from django.contrib import admin
    
    from yourproject.yourapp.models import YourModel
    
    class YourModelAdmin(admin.ModelAdmin):
    
        class Meta:
            model = YourModel
    
        def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
            # Name of your field here
            if db_field.name == 'add_only':
                if request:
                    add_opts = (self._meta.app_label, self._meta.module_name)
                    add = u'/admin/%s/%s/add/' % add_opts
                    if request.META['PATH_INFO'] == add:
                        field = db_field.formfield(**kwargs)
                    else:
                        kwargs['widget'] = forms.HiddenInput()
                        field = db_field.formfield(**kwargs)
                return field
            return admin.ModelAdmin(self, db_field, request, **kwargs)
    
    0 讨论(0)
  • 2020-12-04 10:12

    If you want to set all fields as read only just on the change view, override the admin's get_readonly_fields:

    def get_readonly_fields(self, request, obj=None):
        if obj: # editing an existing object
            # All model fields as read_only
            return self.readonly_fields + tuple([item.name for item in obj._meta.fields])
        return self.readonly_fields
    

    And if you want to hide save buttons on change view:

    1. Change the view

      def change_view(self, request, object_id, form_url='', extra_context=None):
          ''' customize edit form '''
          extra_context = extra_context or {}
          extra_context['show_save_and_continue'] = False
          extra_context['show_save'] = False
          extra_context['show_save_and_add_another'] = False # this not works if has_add_permision is True
          return super(TransferAdmin, self).change_view(request, object_id, extra_context=extra_context)
      
    2. Change permissions if user is trying to edit:

      def has_add_permission(self, request, obj=None):
         # Not too much elegant but works to hide show_save_and_add_another button
          if '/change/' in str(request):
              return False
          return True
      

      This solution has been tested over Django 1.11

    0 讨论(0)
提交回复
热议问题