Remove “add another” in Django admin screen

后端 未结 10 518
孤独总比滥情好
孤独总比滥情好 2020-12-05 13:34

Whenever I\'m editing object A with a foreign key to object B, a plus option \"add another\" is available next to the choices of object B. How do I remove that option?

相关标签:
10条回答
  • 2020-12-05 13:52

    N.B. Works for DJango 1.5.2 and possibly older. The can_add_related property appeared around 2 years ago.

    The best way I've found is to override your ModelAdmin's get_form function. In my case I wanted to force the author of a post to be the currently logged in user. Code below with copious comments. The really important bit is the setting of widget.can_add_related:

    def get_form(self,request, obj=None, **kwargs):
        # get base form object    
        form = super(BlogPostAdmin,self).get_form(request, obj, **kwargs)
    
        # get the foreign key field I want to restrict
        author = form.base_fields["author"]
    
        # remove the green + by setting can_add_related to False on the widget
        author.widget.can_add_related = False
    
        # restrict queryset for field to just the current user
        author.queryset = User.objects.filter(pk=request.user.pk)
    
        # set the initial value of the field to current user. Redundant as there will
        # only be one option anyway.
        author.initial = request.user.pk
    
        # set the field's empty_label to None to remove the "------" null 
        # field from the select. 
        author.empty_label = None
    
        # return our now modified form.
        return form
    

    The interesting part of making the changes here in get_form is that author.widget is an instance of django.contrib.admin.widgets.RelatedFieldWidgetWrapper where as if you try and make changes in one of the formfield_for_xxxxx functions, the widget is an instance of the actual form widget, in this typical ForeignKey case it's a django.forms.widgets.Select.

    0 讨论(0)
  • 2020-12-05 14:02

    Based on cethegeek answer I made this:

    class SomeAdmin(admin.ModelAdmin):
        form = SomeForm
    
        def formfield_for_dbfield(self, db_field, **kwargs):
            formfield = super(SomeAdmin, self).formfield_for_dbfield(db_field, **kwargs)
            if db_field.name == 'some_m2m_field':
                request = kwargs.pop("request", None)
                formfield = self.formfield_for_manytomany(db_field, request, **kwargs)  # for foreignkey: .formfield_for_foreignkey
                wrapper_kwargs = {'can_add_related': False, 'can_change_related': False, 'can_delete_related': False}
                formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(
                    formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs
                )
            return formfield
    
    0 讨论(0)
  • 2020-12-05 14:04

    I use the following approaches for Form and InlineForm

    Django 2.0, Python 3+

    Form

    class MyModelAdmin(admin.ModelAdmin):
        #...
        def get_form(self,request, obj=None, **kwargs):
    
            form = super().get_form(request, obj, **kwargs)
            user = form.base_fields["user"]
    
            user.widget.can_add_related = False
            user.widget.can_delete_related = False
            user.widget.can_change_related = False
    
            return form  
    

    Inline Form

    class MyModelInline(admin.TabularInline):
        #...
        def get_formset(self, request, obj=None, **kwargs):
    
            formset = super().get_formset(request, obj, **kwargs)
            user = formset.form.base_fields['user']
    
            user.widget.can_add_related = False
            user.widget.can_delete_related = False
            user.widget.can_change_related = False
    
            return formset
    
    0 讨论(0)
  • 2020-12-05 14:10

    The answer by @Slipstream shows how to implement the solution, viz. by overriding the attributes for the formfield's widget, but, in my opinion, get_form is not the most logical place to do this.

    The answer by @cethegeek shows where to implement the solution, viz. in an extension of formfield_for_dbfield, but does not provide an explicit example.

    Why use formfield_for_dbfield? Its docstring suggests that it is the designated hook for messing with form fields:

    Hook for specifying the form Field instance for a given database Field instance.

    It also allows for (slightly) cleaner and clearer code, and, as a bonus, we can easily set additional form Field attributes, such as initial value and/or disabled (example here), by adding them to the kwargs (before calling super).

    So, combining the two answers (assuming the OP's models are ModelA and ModelB, and the ForeignKey model field is named b):

    class ModelAAdmin(admin.ModelAdmin):
        def formfield_for_dbfield(self, db_field, request, **kwargs):
            # optionally set Field attributes here, by adding them to kwargs
            formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
            if db_field.name == 'b':
                formfield.widget.can_add_related = False
                formfield.widget.can_change_related = False
                formfield.widget.can_delete_related = False
            return formfield
    
    # Don't forget to register...
    admin.site.register(ModelA, ModelAAdmin)
    

    NOTE: If the ForeignKey model field has on_delete=models.CASCADE, the can_delete_related attribute is False by default, as can be seen in the source for RelatedFieldWidgetWrapper.

    0 讨论(0)
  • 2020-12-05 14:12

    DEPRECATED ANSWER

    Django has since made this possible.


    Have you considered instead using CSS to simply not show the button? Maybe that's a little too hacky.

    This is untested, but I'm thinking...

    no-addanother-button.css

    #_addanother { display: none }
    

    admin.py

    class YourAdmin(admin.ModelAdmin):
        # ...
        class Media:
            # edit this path to wherever
            css = { 'all' : ('css/no-addanother-button.css',) }
    

    Django Doc for doing this -- Media as a static definition

    Note/Edit: The documentation says the files will be prepended with the MEDIA_URL but in my experimentation it isn't. Your mileage may vary.

    If you find this is the case for you, there's a quick fix for this...

    class YourAdmin(admin.ModelAdmin):
        # ...
        class Media:
            from django.conf import settings
            media_url = getattr(settings, 'MEDIA_URL', '/media/')
            # edit this path to wherever
            css = { 'all' : (media_url+'css/no-addanother-button.css',) }
    
    0 讨论(0)
  • 2020-12-05 14:12

    (stop upvoting this wrong answer!!!)

    ERRATA : This answer is basically wrong, and does not answer OP's question. See below.

    (this is only applicable to inline forms, not foreign key fields as OP asked)

    Simpler solution, no CSS hack, no editing Django codebase :

    Add this to your Inline class :

    max_num=0

    UPDATE

    This does not answer OP's question, and is only useful to hide the "add related" button for inline forms, and not foreign keys as requested.

    When I wrote this answer, IIRC the accepted answer hide both, which is why I got confused.

    The following links seems to provide a solution (though hiding using CSS seems the most feasible things to do, especially if the "add another" buttons of FKs in inline forms) :

    Django 1.7 removing Add button from inline form

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