36👍
First have a look at source of ModelAdmin class’ get_form
and get_formsets
methods located in django.contrib.admin.options.py
. You can override those methods and use kwargs to get the behavior you want. For example:
class SoftwareVersionAdmin(ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# Proper kwargs are form, fields, exclude, formfield_callback
if obj: # obj is not None, so this is a change page
kwargs['exclude'] = ['foo', 'bar',]
else: # obj is None, so this is an add page
kwargs['fields'] = ['foo',]
return super(SoftwareVersionAdmin, self).get_form(request, obj, **kwargs)
39👍
This is an old question but I wanted to add that the add_view and change_view methods can be modified for this purpose:
class SoftwareVersionAdmin(ModelAdmin):
...
def add_view(self,request,extra_content=None):
self.exclude = ('product','version_number',)
return super(SoftwareVersionAdmin,self).add_view(request)
def change_view(self,request,object_id,extra_content=None):
self.exclude = ('product','description',)
return super(SoftwareVersionAdmin,self).change_view(request,object_id)
- [Django]-How to execute a GROUP BY … COUNT or SUM in Django ORM?
- [Django]-Generating PDFs from SVG input
- [Django]-How do I get the object if it exists, or None if it does not exist in Django?
8👍
I couldn’t get this working in django 1.6.5 using the above solutions. So I tried creating forms and having get_form serve those pre-defined forms depending on if the object exists or not:
models.py:
from django.db import models
class Project(models.Model):
name = models.CharField('Project Name', max_length=255)
slug = models.SlugField('Project Slug', max_length=255, unique=True)
forms.py:
from django import forms
from models import Project
class ProjectAddForm(forms.ModelForm):
test = forms.Field()
class Meta:
model = Project
class ProjectEditForm(forms.ModelForm):
class Meta:
model = Project
fields = ("name", 'slug')
admin.py
from django.contrib import admin
from models import Project
from forms import ProjectAddForm, ProjectEditForm
class ProjectAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# Proper kwargs are form, fields, exclude, formfield_callback
if obj:
self.form = ProjectEditForm
else:
self.form = ProjectAddForm
return super(ProjectAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(Project, ProjectAdmin)
Now I can intercept the non-persistent test field in the forms clean and do as I wish with it, just overwrite clean in the ProjectAddForm:
def clean(self):
cleaned_data = super(ProjectAddForm, self).clean()
test = cleaned_data.get("test")
# Do logic here
#raise forms.ValidationError("Passwords don't match.")
return cleaned_data
- [Django]-How to run Debug server for Django project in PyCharm Community Edition?
- [Django]-Django – [Errno 111] Connection refused
- [Django]-Django request to find previous referrer
4👍
I don’t think it’s a good idea to override fields
or exclude
or form
, because they are config attributes, so they would not initialize for every request.
I think the accepted answer by shanyu is a good solution.
Or we can use the method from UserAdmin:
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super(UserAdmin, self).get_fieldsets(request, obj)
Remember to assign the add_fieldsets
yourself. Unfortunately it doesn’t fit my use case.
For Django 1.7. I don’t know how they are implemented in other versions.
- [Django]-Celery. Decrease number of processes
- [Django]-VSCode terminal shows incorrect python version and path, launching terminal from anaconda works perfectly
- [Django]-How to check the TEMPLATE_DEBUG flag in a django template?
4👍
This is how it’s done in Django 1.10. Just override get_form
and return add_form
when object is None:
class FoobarAddForm(forms.ModelForm):
class Meta:
model = Foobar
fields = ['some_field',]
@register(Foobar)
class AdminFoobar(admin.ModelAdmin):
add_form = FoobarAddForm
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super(AdminFoobar, self).get_form(request, obj, **defaults)
- [Django]-Django orm get latest for each group
- [Django]-Django : Filter query based on custom function
- [Django]-Understanding Django-LDAP authentication
2👍
This specific code did not work for me.
I simply change it a little:
if obj: # obj is not None, so this is a change page
#kwargs['exclude'] = ['owner']
self.fields = ['id', 'family_name', 'status', 'owner']
else: # obj is None, so this is an add page
#kwargs['fields'] = ['id', 'family_name', 'status']
self.fields = ['id', 'family_name', 'status']
return super(YourAdmin, self).get_form(request, obj, **kwargs)
- [Django]-AttributeError: can't set attribute
- [Django]-Django: Rest Framework authenticate header
- [Django]-Django – How to use decorator in class-based view methods?
2👍
dpawlows‘ solution above is the clearest, I think.
However, I encountered an additional issue in that type of structure.
If change_view()
makes changes to the model, e.g. specifies readonly_fields
that have been filled in in add_view()
, these changes persist in add_view()
after change_view()
has been called. For example:
def add_view(self, request, extra_context=None):
return super().add_view(request)
def change_view(self, request, object_id, extra_context=None):
self.readonly_fields = ['name'] # this change persists in add_view()
return super().change_view(self, request, object_id)
In this case, after change_view()
has been called on any instance, invoking add_view()
will show readonly_fields
(“name”, in this case) set by change_view()
and thus protect these fields from filling in.
This can be solved by adding a ‘roll back’ assignment in add_view()
:
def add_view(self, request, extra_context=None):
self.readonly_fields = [] # 'roll back' for changes made by change_view()
return super().add_view(request)
- [Django]-Django-rest-framework: api versioning
- [Django]-Django: create Index: non-unique, multiple column
- [Django]-What is the correct way of returning an empty queryset in a Django View?
2👍
With more modern Django versions (3.2 at the time of writing), you can override some methods from BaseModelAdmin
to achieve having different fields on the "change" and the "add" model admin page:
class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
"""Functionality common to both ModelAdmin and InlineAdmin."""
# ...
def get_exclude(self, request, obj=None):
"""
Hook for specifying exclude.
"""
return self.exclude
def get_fields(self, request, obj=None):
"""
Hook for specifying fields.
"""
if self.fields:
return self.fields
# _get_form_for_get_fields() is implemented in subclasses.
form = self._get_form_for_get_fields(request, obj)
return [*form.base_fields, *self.get_readonly_fields(request, obj)]
def get_fieldsets(self, request, obj=None):
"""
Hook for specifying fieldsets.
"""
if self.fieldsets:
return self.fieldsets
return [(None, {'fields': self.get_fields(request, obj)})]
def get_readonly_fields(self, request, obj=None):
"""
Hook for specifying custom readonly fields.
"""
return self.readonly_fields
For example, to add some read-only fields on the change page (obj
exists), but not on the add page:
class MyModelAdmin(admin.ModelAdmin):
# Readonly_fields only on change page
def get_readonly_fields(self, request, obj=None):
if obj: # obj is not None, so this is a change page
return 'field_1', 'field_2'
return () # obj is None, so this is the add page
- [Django]-How to run a single test or single TestCase with django-nose?
- [Django]-Django – TypeError – save() got an unexpected keyword argument 'force_insert'
- [Django]-Django DB Settings 'Improperly Configured' Error
0👍
Using formsets in Django 1.6 I ended up with the following:
def get_formsets(self, request, obj=None):
if obj is None:
# It's a new object
for field, fieldset in {'hide_me_from_the_first_fieldset': 0,
'me_from_the_second': 1,
'and_me_too': 1}.items():
self.fieldsets[fieldset][1]['fields'].remove(field)
return super().get_formsets(request, obj)
EDIT:
Perhaps a more intuitive way is to specify a separate add_fieldsets
property and do:
def get_formsets(self, request, obj=None):
if obj is None:
self.fieldsets = self.add_fieldsets
return super().get_formsets(request, obj)
- [Django]-URL encoding on Django Template
- [Django]-How do I install psycopg2 for Python 3.x?
- [Django]-Django test runner not finding tests
0👍
An easy way is to use fieldsets
for the Change Page and add_fieldsets
for the Add Page.
- [Django]-Django-rest-framework accept JSON data?
- [Django]-Django REST Framework: adding additional field to ModelSerializer
- [Django]-Is it possible to generate django models from the database?
0👍
base django 3.2, you can try this
class SoftwareVersionAdmin(ModelAdmin):
fields = ("product", "version_number", "description",
"media", "relative_url", "current_version")
list_display = ["product", "version_number", "size",
"current_version", "number_of_clients", "percent_of_clients"]
list_display_links = ("version_number",)
list_filter = ['product',]
# add
add_fieldsets = (
(None, {
'description': 'add fields',
'fields': ('product', 'description'),
}),
)
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super().get_fieldsets(request, obj)
learn this code from django.contrib.auth.admin.UserAdmin
- [Django]-Django CSRF framework cannot be disabled and is breaking my site
- [Django]-Django: How can I identify the calling view from a template?
- [Django]-Add a custom permission to a User