Cannot hide "Save and add another" button in Django Admin - python

I would like to hide all the "Save" buttons in Django's Admin's Change Form, for a specific model, when certain conditions are met. Therefore, I override the changeform_view method in the relevant ModelAdmin, like so:
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
obj = collection_management_MammalianLine.objects.get(pk=object_id)
if obj:
if not (request.user.is_superuser or request.user.groups.filter(name='Lab manager').exists() or request.user == obj.created_by):
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
extra_context['show_save_and_add_another'] = False
else:
pass
else:
pass
return super(MammalianLinePage, self).changeform_view(request, object_id, extra_context=extra_context)
With this code, I can successfully hide the "Save" and "Save and continue" buttons, but not the "Save and add another" one. I can see that submit_line.html contains the following three lines:
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
My question is: why can I hide the "Save" and "Save and continue" buttons, but not the "Save and add another" one? Even though the relevant templatetag (show_save_and_continue) is in the template.

You may override render_change_form method in ModelAdmin subclass.
In this method obj is available as argument and you can check certain conditions.
class OrderAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
context.update({
'show_save': False,
'show_save_and_continue': False,
'show_save_and_add_another': False,
'show_delete': False
})
return super().render_change_form(request, context, add, change, form_url, obj)

EDIT:
As of Django 3.1, the approach from the original question should simply work:
...
extra_context['show_save_and_add_another'] = False
...
See this Django commit for details.
OLD ANSWER:
summary
Some additional options:
Set save_as=True on your ModelAdmin. As described in the docs, this will replace the "Save and add another" button with a "Save as new" button. This may not be ideal for all cases, but it is the simplest solution as far as I know.
Apply a monkey patch for the has_add_permission method on your ModelAdmin, so it returns False during the call to super().change_view (or changeform_view).
Override the TemplateResponse.content (docs) to simply hide (or remove) the submit-row element, which contains the buttons, from the HTML.
details option 1
The simplest option is to set save_as=True on the ModelAdmin. This will replace the "Save and add another" button with a "Save as new" button. As a result, assuming the other save buttons have been disabled, any changes made to the current object can only be saved as a new object. The current object will remain unchanged.
Basic implementation:
class MyModelAdmin(ModelAdmin):
save_as = True
# your other code here, such as the extended changeform_view
details option 2
The submit_line.html template shows which context variables are used to show/hide the save and delete buttons. Most of those context variables can be set via the extra_context in changeform_view (or change_view). However, as the OP showed, we cannot simply override show_save_and_add_another in that manner.
As pointed out in #Oluwafemi-Sule's answer,
show_save_and_add_another is set in admin_modify.py, which creates the context for submit_line.html.
Upon closer inspection of the source, it is tempting to override the has_add_permission context variable, because that determines the value of show_save_and_add_another. However, simply adding has_add_permission=False to extra_context does not work in Django < 2.1, because the change will be undone by the ModelAdmin.render_change_form method.
Fortunately, rather than overriding the template or patching the template tags, we can simply trick Django into thinking that has_add_permission is False, using a monkey patch in change_view:
def change_view(self, request, object_id=None, form_url='', extra_context=None):
# use extra_context to disable the other save (and/or delete) buttons
extra_context = dict(show_save=False, show_save_and_continue=False, show_delete=False)
# get a reference to the original has_add_permission method
has_add_permission = self.has_add_permission
# monkey patch: temporarily override has_add_permission so it returns False
self.has_add_permission = lambda __: False
# get the TemplateResponse from super (python 3)
template_response = super().change_view(request, object_id, form_url, extra_context)
# restore the original has_add_permission (otherwise we cannot add anymore)
self.has_add_permission = has_add_permission
# return the result
return template_response
This also works if you replace change_view by changeform_view, as used by the OP (source).
Note: obviously, this option can only be used if has_add_permission=False does not interfere with other things in your change view.
details option 3
Based on this example from the docs, it is also possible simply to hide (or even remove) the submit-row from the rendered HTML for the change_view:
def change_view(self, request, object_id=None, form_url='',
extra_context=None):
# get the default template response
template_response = super().change_view(request, object_id, form_url,
extra_context)
# here we simply hide the div that contains the save and delete buttons
template_response.content = template_response.rendered_content.replace(
'<div class="submit-row">',
'<div class="submit-row" style="display: none">')
return template_response
As noted, we can also remove the submit-row section altogether, but that is a bit more work.
This is simpler than option 2, but more fragile.

If no one is against, then I publish my little monkey patch.
This can be inserted anywhere and called up from the settings file (preferably at the very end).
# Hide "save_and_add_another" button
from django.contrib.admin.templatetags import admin_modify
submit_row = admin_modify.submit_row
def submit_row_custom(context):
ctx = submit_row(context)
ctx['show_save_and_add_another'] = False
return ctx
admin_modify.submit_row = submit_row_custom

The other keys are checked for in the passed context except show_save_and_continue. Django always sets this directly.
'show_save_and_add_another': (
context['has_add_permission'] and not is_popup and
(not save_as or context['add'])
),
You can patch the submit_row template tag function to first check the context dictionary for show_save_and_add_another.
#register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
"""
Display the row of buttons for delete and save.
"""
change = context['change']
is_popup = context['is_popup']
save_as = context['save_as']
show_save = context.get('show_save', True)
show_save_and_continue = context.get('show_save_and_continue', True)
show_save_and_add_another = context.get('show_save_and_add_another', False)
ctx = Context(context)
ctx.update({
'show_delete_link': (
not is_popup and context['has_delete_permission'] and
change and context.get('show_delete', True)
),
'show_save_as_new': not is_popup and change and save_as,
'show_save_and_add_another': (
context.get('show_save_and_add_another', None) or
(context['has_add_permission'] and not is_popup and
(not save_as or context['add']))
),
'show_save_and_continue': not is_popup and context['has_change_permission'] and show_save_and_continue,
'show_save': show_save,
})
return ctx
Edit
Steps to patch the "admin/submit_line.html" inclusion tag
Create a templatetags folder at the same level of models.py and views.py
Create __init__.py in the templatetags folder
Copy django/contrib/admin/templatetags/admin_modify.py to templatetags/admin_modify.py.
Overwrite submit_row function definition with the one above.
Note that this is applicable for Django 2.0 and below.
For recent Django versions, find a context mix that allows this expression to be False.e.g.
has_add_permission and not is_popup and
(not save_as or add) and can_save
See values for the names used in the above expression.

I have an alternative way to hide the "Save and add another" button.
In form.py
class TestForm(forms.ModelForm):
class Media:
js = ('admin/yourjsfile.js',)
In yourjsfile.js
(function($) {
'use strict';
$(document).ready(function() {
// hide the "Save and add another" button
$("input[name='_addanother']").attr('type','hidden');
});
})(django.jQuery);
I had same issue for hiding that button.
I've tested it in Django 1.11 and it did work, but I think there are better ways to achieve it.

I propose (changed option 3 of djvg's answer) removing this html input element with regex as with this example:
class MyModelAdmin(admin.ModelAdmin):
def add_view(self, request, form_url='', extra_context=None):
template_response = super(MyModelAdmin, self).add_view(
request, form_url=form_url, extra_context=extra_context)
# POST request won't have html response
if request.method == 'GET':
# removing Save and add another button: with regex
template_response.content = re.sub("<input.*?_addanother.*?(/>|>)", "", template_response.rendered_content)
return template_response
_addanother is this html element's id

To remove "Save and add another" button, return "False" in "has_add_permission()" as shown below and I don't know why we cannot remove "Save and add another" button by setting "False" to "extra_context['show_save_and_another']" in "changeform_view()":
# "admin.py"
from django.contrib import admin
from .models import MyModel
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None): # Here
return False

Related

Django TabularInline override admin temaplate

I have a class that inherits from TabularInline. I have overridden the template, how do I pass data to this template to output it?
class commercial_proposal_productadmin(SortableTabularInline):
model = commercial_proposal_product
extra = 0
template = "admin/tabular.html"
i tried to use the change_view function, but the world value is not displayed in the template
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = {'world':'hello'}
return super().change_view(request, object_id, form_url, extra_context=extra_context)
The template code is required. But in order to display something in the template, it is necessary to output data from the context with a special tag of the Django template engine:
{{ world }}
https://docs.djangoproject.com/en/4.1/ref/templates/language/

Is there a 'DetailView' in Django Admin?

I know there is a change/update view in Django admin but is there any detail view that just lists out the record's attributes? Kind of like the DetailView in the Django app?
Or does anyone know any 3rd party packages I can install to provide the same functionality?
I too was investigating this recently.
One approach that works is to create a custom ModelAdmin with a detail_view method that forwards the call to ModelAdmin's changeform_view() method. Then this view is added to the urls list via overriding ModelAdmin.get_urls().
Then, in this method set a class attribute, say __detail_view to True. Then override has_change_permission() method, which returns False, if __detail_view is detected and set to True. This will cause AdminSite to render the fields in readonly mode (using the AdminReadonlyField wrapper fields) instead of the standard AdminField objects.
You can also change the change_form_template to a custom template for detail_view to accommodate custom rendering for detail views.
class CustomModelAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if getattr(self, '__detail_view', None):
return False
return super().has_change_permission(request, obj)
def detail_view(self, request, object_id, form_url='', extra_context=None):
setattr(self, '__detail_view', True)
# Custom template for detail view
org_change_form_template = self.change_form_template
self.change_form_template = self.detail_view_template or self.change_form_template
ret = self.changeform_view(request, object_id, form_url, extra_context)
self.change_form_template = org_change_form_template
delattr(self, '__detail_view')
return ret
def get_urls(self):
urls = super().get_urls()
# add detail-view for the object
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
# Replace the backwards compatibility (Django<1.9) change view
# for the detail view.
urls[len(urls)-1] = path('<path:object_id>/', wrap(self.detail_view), name='%s_%s_detail' % info)
return urls
I haven't tried the custom template approach, but using the __detail_view object attribute to force readonly rending seems to work.
The default change_form_template still shows the delete button at the bottom, which is okay I guess. But it needs another button to actually take you to the real change page where the object can be changed. Again template customization is the way to go here. Hint: look at {% submit_row %} in admin templates and model a custom inclusion template tag that displays the Change button, if the user has change permission. Take note to call the has_change_permission() here to get the real permission before setting the __detail_view attribute.
Not sure if there are other implications for doing it this way, but it ought to work.
HTH

Django formwizard - cannot get data from step 1 to step 2

I am using a Django form wizard to enter data into a form page, and then display it in a confirmation page. However, when I try to call self.get_cleaned_data_for_step(step_name), I get a "'MyForm' object has no attribute 'cleaned_data'." I know this can happen if the form fails to validate, so I overrode the is_valid method in my form class to always return True, just as a test, but I still get this error. My relevant code is below:
forms.py
...
class MealForm(forms.Form):
modifications = forms.CharField()
def __init__(self, *args, **kwargs):
menu_items = kwargs.pop('menu_items')
super(MealForm, self).__init__(*args, **kwargs)
for item in menu_items:
self.fields[str(item.name)] = forms.IntegerField(widget=forms.NumberInput(attrs={'value': 0}))
def is_valid(self):
return True
urls.py
...
url(r'^(?P<url>[-\w]+)/meal/$',
login_required(views.MealFormWizard.as_view(views.MealFormWizard.FORMS)), name="meal"),
views.py
...
class MealFormWizard(SessionWizardView):
FORMS = [('meal_form', MealForm),
('meal_form_confirmation', MealFormConfirmation)]
TEMPLATES = {'meal_form': 'restaurant/createMeal.html',
'meal_form_confirmation': 'restaurant/confirmation.html'}
def get_form_kwargs(self, step=None):
kwargs = {}
url = self.kwargs['url']
restaurant = Restaurant.objects.get(url=url)
menu_items = MenuItem.objects.filter(restaurant=restaurant)
if step == 'meal_form':
kwargs['menu_items'] = menu_items
return kwargs
def get_context_data(self, form, **kwargs):
context = super(MealFormWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'meal_form':
context.update({...objects/vars...})
if self.steps.current == 'meal_form_confirmation':
cd = self.get_cleaned_data_for_step('meal_form') **This is where my error occurs**
createMeal.html
...
<form action="" method="post">
{% csrf_token %}
{{ wizard.management_form }}
{{ wizard.form }}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.next }}">Submit Meal</button>
</form>
Upon submitting the form in createMeal.html I should be able to access the cleaned data for the previous step in the get_context_data method of my MealFormWizard class in views.py via a call to self.get_cleaned_data_for_step('meal_form'). However, this is not the case, but I am not sure where I went wrong.
Overriding is_valid like that won't work - if you follow the code you will see that the form's cleaned_data attribute is set by the normal is_valid method.
The docs say that if the form is invalid, then get_cleaned_data_for_step will return None, you need to write your code so it can handle this.
In case this is helpful to anyone. My problem was that in my createMeal.html, my button was simply taking the wizard to the next step, bypassing any validation. The proper solution is to make a simple submit button to submit the form, at which point the wizard will then validate the form, and if it is valid, it will move on to the next step.

Django Admin- disable Editing and remove "Save" buttons for a specific model

I have a Django Model which I wish to be only readonly. No adds and edits allowed.
I have marked all fields readonly and overridden has_add_permission in ModelAdmin as:
class SomeModelAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
Is there a similar has_edit_permission? Which can be disabled to remove "Save" and "Save and continue" buttons? And replace by a simple "Close and Return" button.
Django Documentation Only mentions only about read only fields not about overriding edit permissions.
For Django 1.11:
def has_add_permission(self, request, obj=None):
return False
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save_and_continue'] = False
extra_context['show_save'] = False
return super(YourModelAdmin, self).changeform_view(request, object_id, extra_context=extra_context)
The easiest method would be disabling respective permissions in ModelAdmin class. For example, I have a Cart model that I want an admin to only view (list and details) and all I did was to add the following functions to CartAdmin class to disable delete, change and add
class CartAdmin(admin.ModelAdmin):
list_display = ['listing']
def has_add_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
The three methods has_add_permission, has_change_permission and has_delete_permission are the ones that disable add button, add form, edit form and delete buttons in the admin
Here is a sample output when viewing a record details in the admin that has the above permissions disabled
As you can see the diagram above, you only have close button and the details are not displayed in a form
I had same problem. I fixed it in admin.py
from django.contrib.admin.templatetags.admin_modify import register, submit_row as original_submit_row
#register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
''' submit buttons context change '''
ctx = original_submit_row(context)
ctx.update({
'show_save_and_add_another': context.get('show_save_and_add_another',
ctx['show_save_and_add_another']),
'show_save_and_continue': context.get('show_save_and_continue',
ctx['show_save_and_continue']),
'show_save': context.get('show_save',
ctx['show_save']),
'show_delete_link': context.get('show_delete_link', ctx['show_delete_link'])
})
return ctx
In MyModelAdmin class, add following function
#classmethod
def has_add_permission(cls, request):
''' remove add and save and add another button '''
return False
def change_view(self, request, object_id, extra_context=None):
''' customize add/edit form '''
extra_context = extra_context or {}
extra_context['show_save_and_continue'] = False
extra_context['show_save'] = False
return super(MyModelAdmin, self).change_view(request, object_id, extra_context=extra_context)
Override the templates/admin/submit_line.html template and make the buttons whatever you want. You can do this for only the specific model by putting it in templates/admin/[app_label]/[model]/submit_line.html.
To conditionally show the default submit line or your custom submit line, override ModelAdmin.change_view, and add a boolean to extra_context:
class MyModelAdmin(admin.ModelAdmin):
...
def change_view(self, request, object_id, extra_context=None):
if not request.user.is_superuser:
extra_context = extra_context or {}
extra_context['readonly'] = True
return super(MyModelAdmin, self).change_view(request, object_id, extra_context=extra_context)
Updated answer using Django 1.8 (Python 3 syntax).
There are three things to do:
1) extend the admin change form template, adding an if to conditionally suppress the submit buttons
2) override admin.ModelAdmin.change_view() and set a context var for the template if to read
3) prohibit unwanted POST requests (from DOM hacking, curl/Postman)
MyProject/my_app/templates/admin/my_app/change_form.html
{% extends "admin/change_form.html" %}
{% load admin_modify %}
{% block submit_buttons_top %}{% if my_editable %}{% submit_row %}{% endif %}{% endblock %}
{% block submit_buttons_bottom %}{% if my_editable %}{% submit_row %}{% endif %}{% endblock %}
MyProject/my_app/admin.py (MyModelAdmin)
def change_view(self, request, object_id, form_url='', extra_context=None):
obj = MyModel.objects.get(pk=object_id)
editable = obj.get_status() == 'Active'
if not editable and request.method == 'POST':
return HttpResponseForbidden("Cannot change an inactive MyModel")
more_context = {
# set a context var telling our customized template to suppress the Save button group
'my_editable': editable,
}
more_context.update(extra_context or {})
return super().change_view(request, object_id, form_url, more_context)
I had the same problem - the easiest way to do this, is to include some custom JS.
In you admin.py file include
class Media:
js = ('/static/js/admin.js',)
Then in your admin.js file, include the following JS.
(function($) {
$(document).ready(function($) {
$(".submit-row").hide()
});
})(django.jQuery);
The row is gone - it should work in all versions of Django too.
This has been possible for a while. The names are has_add_permission, has_change_permission and has_delete_permission. See the django admin documentation for reference. Here is also an example:
#admin.register(Object)
class Admin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
Aug, 2022 Update:
You can remove "SAVE" button, "Save and continue editing" button, "Save and add another" button and "Delete" button from a specific admin.
For example, this is "Person" model in "store" app below:
# "store/models.py"
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __str__(self):
return self.first_name + " " + self.last_name
class Meta:
verbose_name = "Person"
verbose_name_plural = "Person"
Then, this is "Person" admin in "store" app below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
pass
Then, this is how "Add person" page looks like:
Then, this is how "Change person" page looks like:
Then, this is how "Select person to change" page looks like:
Then, this is how "Person" admin on "Store administration" page looks like:
First, to remove "SAVE" button, set "False" to "extra_context['show_save']" in "changeform_view()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False # Here
return super().changeform_view(request, object_id, form_url, extra_context)
Then, "SAVE" button is removed from "Add person" page and "Change person" page. *Actually, "SAVE" button is replaced with "Close" buttom as shown below:
Next, to remove "Save and continue editing" button, set "False" to "extra_context['show_save_and_continue']" in "changeform_view()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False # Here
return super().changeform_view(request, object_id, form_url, extra_context)
Then, "Save and continue editing" button is removed from "Add person" page and "Change person" page as shown below:
Next, to remove "Save and add another" button, return "False" in "has_add_permission()" as shown below. *After this, "Add person" page can no longer be accessed:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None): # Here
return False
Then, "Save and add another" button is removed from "Change person" page as shown below:
Then, "ADD PERSON" button is also removed from "Select person to change" page as shown below:
Then, "➕ADD" button is also removed from "Person" admin on "Store administration" page as shown below:
Next, to remove "Delete" button, set "False" to "extra_context['show_delete']" in "changeform_view()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
extra_context['show_delete'] = False # Here
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None):
return False
Then, "Delete" button is removed from "Change person" page as shown below:
Actually, you can also remove "Delete" button by returning "False" in "has_delete_permission()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
# extra_context['show_delete'] = False
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None): # Here
return False
Then, "Delete" button is removed from "Change person" page as shown below:
Then, "Action" select dropdown box is also removed from "Select person to change" page as shown below:
In addition, you can make the fields on "Change person" page unchangeable by returning "False" in "has_change_permission()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
# extra_context['show_delete'] = False
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None): # Here
return False
Then, the fields on "Change person" page are made unchangeable as shown below:
Then, "✏️Change" button is replaced with "👁️View" for "Person" admin on "Store administration" page as shown below:
In addition, you can remove "Person" admin from "Store administration" page by returning "False" in "has_view_permission()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_save'] = False
extra_context['show_save_and_continue'] = False
# extra_context['show_delete'] = False
return super().changeform_view(request, object_id, form_url, extra_context)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_view_permission(self, request, obj=None): # Here
return False
Then, "Person" admin is removed from "Store administration" page as shown below:
Finally, you can replace "changeform_view()" with "render_change_form()" which can also remove "SAVE" button, "Save and continue editing" button and "Delete" button with "context.update()" as shown below:
# "store/admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
# Here
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
context.update({
'show_save': False, # Here
'show_save_and_continue': False, # Here
# 'show_delete': False, # Here
})
return super().render_change_form(request, context, add, change, form_url, obj)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_view_permission(self, request, obj=None):
return False
You could try this package Django Admin View Permission. This package adds a view permission for the specified models and handles the other stuff automatically.
Based on the excellent answer from #mat_gessel, here's my solution:
The main differences are UX'y:
use messages and redirect (with reversing-admin-urls), rather than HttpResponseForbidden to prevent a save
define get_readonly_fields conditionally to prevent un-saveable inputs being displayed
Also:
override change_form.html app-wide, because read_only is such a useful, non-invasive enhancement
define has_delete_permission (may not be required by the OP)
test request.method != 'GET' to prevent PATCH and friends (not altogether sure if this is required, tbh)
my_app/admin.py
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.contrib import admin
from django.contrib import messages
class MyModelAdmin(admin.ModelAdmin):
# let's assume two fields...
fields = (field1, field2)
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
if object_id:
extra_context = extra_context or {}
extra_context['read_only'] = True
if request.method != 'GET':
messages.error(request, "Cannot edit a MyModel object")
return redirect(
reverse('admin:myapp_mymodel_change', args=[object_id])
)
return super(MyModelAdmin, self).changeform_view(request, object_id, extra_context=extra_context)
def has_delete_permission(self, request, obj=None):
return False
def get_readonly_fields(self, request, obj=None):
if obj:
# display all fields as text, rather than inputs
return (field1, field2)
else:
return []
admin/change_form.html
{% extends "admin/change_form.html" %}
{% load admin_modify %}
{# remove the save buttons if read_only is truthy #}
{% block submit_buttons_top %}{% if not read_only %}{% submit_row %}{% endif %}{% endblock %}
{% block submit_buttons_bottom %}{% if not read_only %}{% submit_row %}{% endif %}{% endblock %}
(Tested on Django 1.9: heads up: some imports have moved since then, eg reverse)

Alter Django admin change list title text

I'm creating some custom views for the Django admin interface that use the standard change-list as an interim stage. This works fine, apart from the fact the change-list page H1 is 'Select object to change'. 'Change' is not the right verb for the action the user will be undertaking in my custom views.
I have found the django.contrib.admin templates that control the layout of the change-list pages (change_list.html and change_list_results.html) but I cannot find where the title is supplied from. I'm guessing it is a variable passed in by a view someplace?
How can I override this text with something less misleading e.g. 'Select object' instead of 'Select object to change'? I'm OK with changing it across all the change-list views, not just the particular ones I'm trying to customise; but I'd prefer a solution that is an override as opposed to a modification of the django.contrib.admin code if possible.
Update: I have found the view responsible for the change list, it's main.py in django\contrib\admin\views. The variable is self.title on line 69 (Django 1.0). I have acheived the result I am looking for by editing this line
self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
to read
self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s') % force_unicode(self.opts.verbose_name))
I'd still be really interested to hear a better way of achieving the same result that doesn't involve hacking the django.contrib.admin code - it looks like there already is an option to have the title the way I'd like it, but I'm not sure how to trigger that?
Not sure if still relevant, but another way to do this would be passing the extra_content for the changelist_view method. For ex:
from django.contrib import admin
class MyCustomAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
extra_context = {'title': 'Change this for a custom title.'}
return super(MyCustomAdmin, self).changelist_view(request, extra_context=extra_context)
For current versions of Django:
class CustomChangeList(django.contrib.admin.views.main.ChangeList):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title = 'My Cool Title'
class MyAdmin(ModelAdmin):
def get_changelist(self, request, **kwargs):
return CustomChangeList
There is already ticket for ChangeList customization: http://code.djangoproject.com/ticket/9749. This will give the ability to change many additional aspects of admin application. Unfortunately there is no clean way to achieve your goals.
You can change the title on "Select ... to change" page with "extra_context" in "changelist_view()" as shown below:
# "admin.py"
from django.contrib import admin
from .models import Person
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
extra_context = {'title': 'This is a custom title.'} # Here
return super().changelist_view(request, extra_context=extra_context)
This is how the title on "Select Person to change" page looks like as shown below:
You can override the method and pass it the title in extra_content, see:
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = {'title': 'Hello Title'}
return super(BlogAdmin, self).change_view(request, object_id,
form_url, extra_context=extra_context)
As of Django 3.1.7
I think the OP is asking about the changelist "content" title (the one shown on the page below breadcrumbs, not in the browser tab title). Django sets it from the model's verbose_name_plural (set in model class' class Meta). If it is not explicitly set, Django uses the model class name with 's' suffixed. Here is the code from Django admin change_list.html:
<!-- CONTENT-TITLE -->
{% block content_title %}
<h1>{{ cl.opts.verbose_name_plural|capfirst }}</h1>
{% endblock %}
So if just setting the verbose_name_plural does not suffice/work for you, consider overriding the change_list.html template and do your thing in the {% block content_title %}. If it is too complicated to do in the template, you can pass your own context data to the admin template as given in the SO answer here:
Django how to pass custom variables to context to use in custom admin template?

Categories