Access inline object in InlineModelAdmin form - django

I have such a problem: I want to limit foreign key choices in inline model admin, but I want to do this based on some values specified in this inline object.
How can I access inline object (not parent object) inside InlineModelAdmin methods (specifically in formfield_for_foreignkey(self, db_field, request, **kwargs))?

You cannot access to an inline object inside any InlineModelAdmin methods.
The only way is to use a custom ModelForm which receive instance when is initialized. Pay attention that extra forms to create new inline object receive and "empty" instance (pk=None).
from django.forms import ModelForm
from django.contrib.admin import ModelAdmin, TabularInline
class QuestionForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
# do what you want with inline object instance
class Meta:
model = Question
fields = "__all__"
class QuestionInline(TabularInline):
model = Question
form = QuestionForm
class CategoryAdmin(ModelAdmin):
model = Category
inlines = [QuestionInline]

This should work for you. Change "YourModelAdmin", "target_field", "your_custom_condition" and '...' with your proper values.
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(YourModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'target_field':
if request and your_custom_condition:
field.queryset = field.queryset.filter(...)
return field

Related

Django: Current User Id for ModelForm Admin

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

Django model form - Exclude a field that has no model field

I have a simple model form what I use through the admin interface. Some of my model fields store datas that require a bit more time to calculate (they come from other sites). So I decided to put an extra boolean field to the form to decide to crawl these datas again or not.
class MyModelForm(forms.ModelForm):
update_values = forms.BooleanField(required=False) #this field has no model field
class Meta:
model = MyModel
This extra field doesn't exist in the model because only the form needs it.
The problem is that I only want it to appear if it's an existing record in the database.
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
if self.instance.pk is None:
#remove that field somehow
I tried nearly everything. Exclude it, delete the variable but nothing wants to work. I also tried dynamically add the field if self.instance.pk is exists but that didn't work too.
Any idea how to do the trick?
Thanks for your answers.
You could subclass the form and add the extra field in the subclass:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
class MyUpdateModelForm(MyModelForm):
update_values = forms.BooleanField(required=False) #this field has no model field
class Meta:
model = MyModel
You can then override the get_form method of your admin, which is passed the current instance: get_form(self, request, obj=None, **kwargs)
Rather than removing the field in __init__ if instance.pk is not None, how about adding it if it is None? Remove the class-level declaration and just change the logic:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk is not None:
self.fields['update_values'] = forms.BooleanField(required=False)

CBV Django Form View set data for ChoiceField

I'm using the Django Form View and I want to enter custom choices per user to my Choicefield.
How can I do this?
Can I use maybe the get_initial function?
Can I overwrite the field?
When I want to change certain things about a form such as the label text, adding required fields or filtering a list of choices etc. I follow a pattern where I use a ModelForm and add a few utility methods to it which contain my overriding code (this helps keep __init__ tidy). These methods are then called from __init__ to override the defaults.
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
self.set_querysets()
self.set_labels()
self.set_required_values()
self.set_initial_values()
def set_querysets(self):
"""Filter ChoiceFields here."""
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
def set_labels(self):
"""Override field labels here."""
pass
def set_required_values(self):
"""Make specific fields mandatory here."""
pass
def set_initial_values(self):
"""Set initial field values here."""
pass
If the ChoiceField is the only thing you're going to be customising, this is all you need:
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
You can then make your FormView use this form with like this:
class ProfileFormView(FormView):
template_name = "profile.html"
form_class = ProfileForm

Django ModelForms, override a field, keep the same label

In my ModelForm, I have to override some settings of the fields (e.g. choices, or required state). This requires declaring the entire field again as formfield.
Is there a simple way to access the verbose_name of the model field, so this doesn't have to redefined?
You don't have to redefine the field to change these settings. You can access the field in the form __init__ like below.
class MyForm(forms.ModelForm):
class Meta(object):
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_field'].required = True

Django admin - remove field if editing an object

I have a model which is accessible through the Django admin area, something like the following:
# model
class Foo(models.Model):
field_a = models.CharField(max_length=100)
field_b = models.CharField(max_length=100)
# admin.py
class FooAdmin(admin.ModelAdmin):
pass
Let's say that I want to show field_a and field_b if the user is adding an object, but only field_a if the user is editing an object. Is there a simple way to do this, perhaps using the fields attribute?
If if comes to it, I could hack a JavaScript solution, but it doesn't feel right to do that at all!
You can create a custom ModelForm for the admin to drop the field in the __init__
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
del self.fields['field_b']
class FooAdmin(admin.ModelAdmin):
form = FooForm
EDIT: Taking a hint from John's comment about making the field read-only, you could make this a hidden field and override the clean to ensure the value doesn't change.
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
self.fields['field_b'].widget = forms.HiddenInput()
def clean_field_b(self):
if self.instance and self.instance.pk:
return self.instance.field_b
else:
return self.cleaned_data['field_b']
You can also do the following
class FooAdmin(admin.ModelAdmin)
def change_view(self, request, object_id, extra_context=None):
self.exclude = ('field_b', )
return super(SubSectionAdmin, self).change_view(request, object_id, extra_context)
Taken from here Django admin: exclude field on change form only