How can you limit Django's ModelAdmin inlines to edit mode. This is what I tried but it does not work:
class PageAdmin(admin.ModelAdmin):
form = PageModelForm
def __init__(self, *args, **kwargs):
super(PageAdmin, self).__init__(*args, **kwargs)
if self.instance.pk:
self.inlines = (FileInLine, )
In other word, I would like to access the instance in the ModelAdmin to determine whether pk is set and only in that case shows the inline.
Try this
class PageAdmin(admin.ModelAdmin):
form = PageModelForm
inlines = [FileInLine]
def get_formsets_with_inlines(self, request, obj=None):
for inline in self.get_inline_instances(request, obj):
if isinstance(inline, FileInLine) and obj is None:
continue
yield inline.get_formset(request, obj), inline
https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_formsets_with_inlines
Related
I want for filter a ModelChoiceField with the current user. I found a solution very close that I want to do, but I dont understand
Django: How to get current user in admin forms
The answer accepted says
"I can now access the current user in my forms.ModelForm by accessing self.current_user"
--admin.py
class Customer(BaseAdmin):
form = CustomerForm
def get_form(self, request,obj=None,**kwargs):
form = super(Customer, self).get_form(request, **kwargs)
form.current_user = request.user
return form
--forms.py
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.filter(tenant=????))
class Meta:
model = fa_customers
How do I get the current user on modelchoice queryset(tenant=????)
How do I call the self.current_user in the modelform(forms.py)
Override __init__ constructor of the CustomerForm:
class CustomerForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['default_tax'].queryset =
fa_tax_rates.objects.filter(tenant=self.current_user))
Queryset in the form field definition can be safely set to all() or none():
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.none())
Just to sum up the solution because it was very hard for me to make this work and understand the accepted answer
In admin.py
class MyModelForm (forms.ModelForm):
def __init__(self, *args,**kwargs):
super (MyModelForm ,self).__init__(*args,**kwargs)
#retrieve current_user from MyModelAdmin
self.fields['my_model_field'].queryset = Staff.objects.all().filter(person_name = self.current_user)
#The person name in the database must be the same as in Django User, otherwise use something like person_name__contains
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
def get_form(self, request, *args, **kwargs):
form = super(MyModelAdmin, self).get_form(request, *args, **kwargs)
form.current_user = request.user #get current user only accessible in MyModelAdminand pass it to MyModelForm
return form
Have a inline form class:
class ItemColorSelectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ItemColorSelectForm, self).__init__(*args, **kwargs)
#here i need current object
Inline class:
class ItemColorSelectInline(generic.GenericTabularInline):
model = ColorSelect
extra = 1
form = ItemColorSelectForm
Admin class
class ItemAdmin(admin.ModelAdmin):
inlines = [ItemColorInline,]
Question: how can a get current object in ItemColorSelectForm.
print kwargs return:
{'auto_id': u'id_%s', 'prefix': u'catalog-colorselect-content_type-object_id-__prefix__', 'empty_permitted': True}
Currently accepted solution is not thread safe. If you care about thread safety, never, ever assign an instance to a static class property.
Thread safe solutions are:
For Django 1.7 < 1.9 (possibly earlier versions, unclear):
from django.utils.functional import cached_property
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
self.instance = kwargs['instance']
super(ProxyFormSet, self).__init__(*args, **kwargs)
#cached_property
def forms(self):
kwargs = {'instance': self.instance}
forms = [self._construct_form(i, **kwargs)
for i in xrange(self.total_form_count())]
return forms
return ProxyFormSet
As of Django >= 1.9 it's also possible to pass form_kwargs:
def get_formset(self, *args, **kwargs):
FormSet = super(InlineAdmin, self).get_formset(*args, **kwargs)
class ProxyFormSet(FormSet):
def __init__(self, *args, **kwargs):
form_kwargs = kwargs.pop('form_kwargs', {})
form_kwargs['instance'] = kwargs['instance']
super(ProxyFormSet, self).__init__(
*args, form_kwargs=form_kwargs, **kwargs)
return ProxyFormSet
Above solutions will make an instance kwarg available in the model form:
class InlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InlineForm, self).__init__(*args, **kwargs)
print('instance', kwargs['instance'])
Solution:
Override the formset method in Inline class
def get_formset(self, request, obj=None, **kwargs):
InlineForm.obj = obj
return super(InlineAdmin, self).get_formset(request, obj, **kwargs)
To fix: currently accepted solution not safe in multi-thread mode
Arti's solution works, another better option could be:
Instead of passing the current object id into the inline form,
use the object id to create a inline form field within the get_formset().
# admin.py
class TransactionInline(admin.TabularInline):
model = Transaction
form = TransactionInlineForm
def get_formset(self, request, obj=None, **kwargs):
# comment Arti's solution
# TransactionInlineForm.project_id = obj.id
formset = super().get_formset(request, obj, **kwargs)
field = formset.form.declared_fields['purchase']
field.queryset = get_object_or_404(Project, pk=obj.id).products.all()
return formset
# forms.py
class TransactionInlineForm(ModelForm):
purchase = ModelChoiceField(queryset=None, label='Purchase', required=False)
So, there is no need to override the __init__() in form anymore, neither the current object.
works in Django 2.1.7
I'm having some trouble overriding the queryset for my inline admin.
Here's a bog-standard parent admin and inline admin:
class MyInlineAdmin(admin.TabularInline):
model = MyInlineModel
def queryset(self, request):
qs = super(MyInlineAdmin, self).queryset(request)
return qs
class ParentAdmin(admin.ModelAdmin):
inlines = [MyInlineAdmin]
admin.site.register(ParentAdminModel, ParentAdmin)
Now I can do qs.filter(user=request.user) or qs.filter(date__gte=datetime.today()) no problem.
But what I need is either the MyInlineModel instance or the ParentAdminModel instance (not the model!), as I need to filter my queryset based on that.
Is it possible to get something like self.instance or obj (like in get_readonly_fields() or get_formset()) inside the queryset() method?
Hope this makes sense. Any help is much appreciated.
class MyInlineAdmin(admin.TabularInline):
model = MyInlineModel
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
"""enable ordering drop-down alphabetically"""
if db_field.name == 'car':
kwargs['queryset'] = Car.objects.order_by("name")
return super(MyInlineAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
class ParentAdmin(admin.ModelAdmin):
inlines = [MyInlineAdmin]
admin.site.register(ParentAdminModel, ParentAdmin)
Im assuming your models look something like:
class MyInlineModel(models.Model):
car=models.Foreignkey(Car)
#blah
for more on this; read the django Docs on formfield_for_foreignkey-->
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
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?
You can override the admin's get_readonly_fields method:
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if obj: # editing an existing object
return self.readonly_fields + ('field1', 'field2')
return self.readonly_fields
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:
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)
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
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
A more pluggable Solution to the great solutions of Bernhard and Mario, adding support for createonly_fields analog to readonly_fields:
class MyModelAdmin(admin.ModelAdmin):
# ModelAdmin configuration as usual goes here
createonly_fields = ['title', ]
def get_readonly_fields(self, request, obj=None):
readonly_fields = list(super(MyModelAdmin, self).get_readonly_fields(request, obj))
createonly_fields = list(getattr(self, 'createonly_fields', []))
if obj: # editing an existing object
readonly_fields.extend(createonly_fields)
return readonly_fields
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)
# ...
FYI: in case someone else runs into the same two problems I encountered:
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)
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)
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)
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
I'm trying to initialize the form attribute for MyModelAdmin class inside an instance method, as follows:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
MyModelAdmin.form = MyModelForm(request.user)
My goal is to customize the editing form of MyModelForm based on the current session. When I try this however, I keep getting an error (shown below). Is this the proper place to pass session data to ModelForm? If so, then what may be causing this error?
TypeError at ...
Exception Type: TypeError
Exception Value: issubclass() arg 1 must be a class
Exception Location: /usr/lib/pymodules/python2.6/django/forms/models.py in new, line 185
Combining the good ideas in Izz ad-Din Ruhulessin's answer and the suggestion by Cikić Nenad, I ended up with a very awesome AND concise solution below:
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.form.request = request #so we can filter based on logged in user for example
return super(CustomModelAdmin, self).get_form(request,**kwargs)
Then just set a custom form for the modeladmin like:
form = CustomAdminForm
And in the custom modelform class access request like:
self.request #do something with the request affiliated with the form
Theoretically, you can override the ModelAdmin's get_form method:
# In django.contrib.admin.options.py
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(kwargs.get("exclude", []))
exclude.extend(self.get_readonly_fields(request, obj))
# if exclude is an empty list we pass None to be consistant with the
# default on modelform_factory
exclude = exclude or None
defaults = {
"form": self.form,
"fields": fields,
"exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
Note that this returns a form class and not a form instance.
If some newbie, as myself, passes here:
I had to define:
class XForm(forms.ModelForm):
request=None
then at the end of the previous post
mfc=modelform_factory(self.model, **defaults)
self.form.request=request #THE IMPORTANT statement
return mfc
i use queryset fot filtering records, maybe this example help you:
.....
.....
def queryset(self, request):
cuser = User.objects.get(username=request.user)
qs = self.model._default_manager.get_query_set()
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
if ordering:
qs = qs.order_by(*ordering)
qs = qs.filter(creator=cuser.id)
return qs
Here is a production/thread-safe variation from nemesisfixx solution:
def get_form(self, request, obj=None, **kwargs):
class NewForm(self.form):
request = request
return super(UserAdmin, self).get_form(request, form=NewForm, **kwargs)
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
get_form = super(CustomModelAdmin, self).get_form(request,**kwargs)
get_form.form.request = request
return get_form
Now in ModelForm, we can access it by
self.request
Example:
class CustomModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TollConfigInlineForm, self).__init__(*args, **kwargs)
request = self.request
user = request.user