Readonly for existing items only in Django admin inline

前端 未结 7 1271
长发绾君心
长发绾君心 2020-12-13 14:27

I have a tabular inline model in the Django admin. I need 1 of the fields to not be changeable after it has been created, but setting it as readonly (via readonly_fields) wh

相关标签:
7条回答
  • 2020-12-13 14:58

    According to this post this issue has been reported as a bug in Ticket15602.

    A workaround would be to override the clean method of the inline model in forms.py and raise an error when an existing inline is changed:

    class NoteForm(forms.ModelForm):
        def clean(self):
            if self.has_changed() and self.initial:
                raise ValidationError(
                    'You cannot change this inline',
                    code='Forbidden'
                )
            return super().clean()
    
        class Meta(object):
            model = Note
            fields='__all__'
    

    The above gives a solution on the model level.

    To raise an error when a specific field is changed, the clean_<field> method can help. For example, if the field is a ForeignKey called category:

    class MyModelForm(forms.Form):
        pass  # Several lines of code here for the needs of the Model Form
    
    # The following form will be called from the admin inline class only
    class MyModelInlineForm(MyModelForm):
        def clean_category(self):
            category = self.cleaned_data.get('category', None)
            initial_value = getattr(
                self.fields.get('category', None), 
                'initial', 
                None
            )
            if all(
                (
                    self.has_changed(),
                    category.id != initial_value,
                )
            ):
                raise forms.ValidationError(
                    _('You cannot change this'),
                    code='Forbidden'
                )
            return category
    
    
        class Meta:
            # Copy here the Meta class of the parent model 
    
    0 讨论(0)
  • 2020-12-13 15:00

    This is possible with a monkey patch.

    The following example will make the "note" field to be read only for existing AdminNote objects. Unlike converting fields to be hidden like suggested in other answers, this will actually remove fields from the submit/validation workflow (which is more secure and uses existing field renderers).

    #
    # app/models.py
    #
    
    class Order(models.Model):
        pass
    
    class AdminNote(models.Model):
        order = models.ForeignKey(Order)
        time = models.DateTimeField(auto_now_add=True)
        note = models.TextField()
    
    
    #
    # app/admin.py
    #
    
    import monkey_patches.admin_fieldset
    
    ...
    
    class AdminNoteForm(forms.ModelForm):
        class Meta:
            model = AdminNote
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.get_readonly_fields():
                del self.fields[field]
    
        def get_readonly_fields(self):
            if self.instance.pk:
                return ['note']
            return []
    
    
    class AdminNoteInline(admin.TabularInline):
        model = AdminNote
        form = AdminNoteForm
        extra = 1
        fields = 'note', 'time'
        readonly_fields = 'time',
    
    
    @admin.register(Order)
    class OrderAdmin(admin.ModelAdmin):
        inlines = AdminNoteInline,
    
    
    #
    # monkey_patches/admin_fieldset.py
    #
    
    import django.contrib.admin.helpers
    
    
    class Fieldline(django.contrib.admin.helpers.Fieldline):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            if hasattr(self.form, 'get_readonly_fields'):
                self.readonly_fields = list(self.readonly_fields) + list(self.form.get_readonly_fields())
    
    django.contrib.admin.helpers.Fieldline = Fieldline
    
    0 讨论(0)
  • 2020-12-13 15:03

    Here's a better read-only widget that I've used before:
    https://bitbucket.org/stephrdev/django-readonlywidget/

    from django_readonlywidget.widgets import ReadOnlyWidget
    
    class TestAdmin(admin.ModelAdmin):
        def formfield_for_dbfield(self, db_field, **kwargs):
            field = super(TestAdmin, self).formfield_for_dbfield(db_field, **kwargs)
            if field:
                field.widget = ReadOnlyWidget(db_field=db_field)
            return field
    
    0 讨论(0)
  • 2020-12-13 15:10

    You can achieve this with only a single inline like so:

    class MyInline(admin.TabularInline):
        fields = [...]
        extra = 0
    
        def has_change_permission(self, request, obj):
            return False
    
    0 讨论(0)
  • 2020-12-13 15:14

    Having the same problem, I came across this fix:

    Create two inline objects, one with no change permission, and the other with all the fields read-only. Include both in the model admin.

    class SubscriptionInline(admin.TabularInline):
        model = Subscription
        extra = 0
        readonly_fields = ['subscription', 'usedPtsStr', 'isActive', 'activationDate', 'purchaseDate']
    
        def has_add_permission(self, request):
            return False
    
    class AddSupscriptionInline(admin.TabularInline):
        model = Subscription
        extra = 0
        fields = ['subscription', 'usedPoints', 'isActive', 'activationDate', 'purchaseDate']
    
        def has_change_permission(self, request, obj=None):
            return False
    
        # For Django Version > 2.1 there is a "view permission" that needs to be disabled too (https://docs.djangoproject.com/en/2.2/releases/2.1/#what-s-new-in-django-2-1)
        def has_view_permission(self, request, obj=None):
            return False
    

    Include them in the same model admin:

    class UserAdmin(admin.ModelAdmin):
        inlines = [ AddSupscriptionInline, SubscriptionInline]
    

    To add a new subscription I use the AddSubscriptionInline in the admin. Once it is saved, the new subscription disappears from that inline, but now does appear in the SubscriptionInline, as read only.

    For SubscriptionInline, it is important to mention extra = 0, so it won't show junk read-only subscriptions. It is better also to hide the add option for SubscriptionInline, to allow adding only via AddSubscriptionInline, by setting the has_add_permission to always return False.

    Not perfect at all, but it's the best option for me, since I must provide the ability to add subscriptions on the user admin page, but after one is added, it should be changed only via the internal app logic.

    0 讨论(0)
  • 2020-12-13 15:15

    This code work perfectly according to your requirements.

    Actually i got this answer from my own question but specific to my problem and i removed some lines related to my problem. And credit goes to @YellowShark. Check here my question.

    Once you created new inline then you will be not able to edit existing inline.

    class XYZ_Inline(admin.TabularInline):
        model = YourModel
    
    class RequestAdmin(admin.ModelAdmin):
    inlines = [XYZ_Inline, ]
    
    # If you wanted to manipulate the inline forms, to make one of the fields read-only:
    def get_inline_formsets(self, request, formsets, inline_instances, obj=None):
        inline_admin_formsets = []
        for inline, formset in zip(inline_instances, formsets):
            fieldsets = list(inline.get_fieldsets(request, obj))
            readonly = list(inline.get_readonly_fields(request, obj))
            prepopulated = dict(inline.get_prepopulated_fields(request, obj))
            inline_admin_formset = helpers.InlineAdminFormSet(
                inline, formset, fieldsets, prepopulated, readonly,
                model_admin=self,
            )
    
            if isinstance(inline, XYZ_Inline):
                for form in inline_admin_formset.forms:
                #Here we change the fields read only.
                        form.fields['some_fields'].widget.attrs['readonly'] = True
    
            inline_admin_formsets.append(inline_admin_formset)
        return inline_admin_formsets
    

    You can add only new inline and read only all existing inline.

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