I'm trying to pre-fill some inlines in Django Admin with data passed as query params (in case of adding a new object in DB).
class TestCaseInlineFormSet(BaseInlineFormSet):
class Meta:
model = TestCase
fields = '__all__'
def __init__(self, *args, **kwargs):
super(TestCaseInlineFormSet, self).__init__(*args, **kwargs)
ids_string = self.request.GET.get('ids')
if ids_string:
ids = [int(x) for x in ids_string.split(',')]
self.initial = [{'test_case': id} for id in ids]
class TestCaseInline(admin.TabularInline):
model = TestCase
raw_id_fields = ('test_case',)
extra = 1
formset = TestCaseInlineFormSet
def get_formset(self, request, obj=None, **kwargs):
formset = super(TestCaseInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
def get_extra(self, request, obj=None, **kwargs):
extra = super(TestCaseInline, self).get_extra(request, obj, **kwargs)
requested_extras = len(request.GET.get('ids', '').split(','))
return max(extra, requested_extras)
The data is pre-filled fine with this solution, however there's an issue when trying to submit: the pre-filled inlines are not marked as changed, so they're not saved.
I've tried overriding has_changed() on the TestCaseInlineFormSet however it doesn't solve the problem - it seems has_changed() for the formset is never called?
Any idea how to fix this?
Related
How do I limit choices of a ForeignKeyField of an InlineForm in Django Admin, depended on the selected object in the AdminForm.
The problem is that InlineForm does not know anything about the object from the related Admin Form.
You have to pass the parent object from the InlineAdmin to the FormSet to the actual Form. Took me quite a moment to figure that out.
In the example below CalibrationCertificateData is relate to the CalibrationCertificate and I only want to show Quantities that are related with the SensorCategory.
from django.contrib import admin
from django.forms import ModelForm, BaseInlineFormSet
from src.admin import site_wm
from . import models
from quantity_management.models import QuantitySensor
# Register your models here.
site_wm.register(models.CalibrationInstitute, site=site_wm)
class CertDataAdminForm(ModelForm):
class Meta:
model = models.CalibrationCertificateData
fields = "__all__"
def __init__(self, *args, **kwargs):
"""
Make sure that we only show the Quantites that are related to the selected Cert>Sensor>Category
"""
self.parent_obj = None
if 'parent_obj' in kwargs:
self.parent_obj = kwargs['parent_obj']
del kwargs['parent_obj']
super().__init__(*args, **kwargs)
if self.parent_obj:
# Do the actual filtering according to the parent object
category_id = self.parent_obj.Sensor.Category_id
self.fields['Quantity'].queryset = QuantitySensor.objects.filter(Sensor_id=category_id)
class CertDataAdminFormSet(BaseInlineFormSet):
form = CertDataAdminForm
def get_form_kwargs(self, index):
"""
Make sure the form knows about the parent object
"""
kwargs = super().get_form_kwargs(index)
if hasattr(self, 'parent_obj') and self.parent_obj:
kwargs['parent_obj'] = self.parent_obj
return kwargs
class CalibrationDataAdmin(admin.StackedInline):
model = models.CalibrationCertificateData
extra = 0
form = CertDataAdminForm
formset = CertDataAdminFormSet
def get_formset(self, request, obj=None, **kwargs):
"""
give the formset the current object, so it can limit the selection choices according to it
"""
formset = super().get_formset(request, obj, **kwargs)
if formset is not None:
formset.parent_obj = obj
return formset
def formfield_for_foreignkey(self, db_field, request, **kwargs):
print(self.parent_model)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
#admin.register(models.CalibrationCertificate, site=site_wm)
class CalibrationCertificateAdmin(admin.ModelAdmin):
inlines = (
CalibrationDataAdmin,
)
Currently Happening:
Dynamically generated form and form fields are being displayed.
Enter some data into the said fields, but self.get_all_cleaned_data() returns nothing.
Form returns to page 0 instead of submitting the form and using done()
What I want to happen:
- Data in fields to be retained and displayed when going back, or to the confirmation page
- Form to actually submit and use done() to process and save
The following the my forms.py
class OrderForm(forms.Form):
class Meta:
localized_fields = ('__all__',)
def __init__(self, *args, **kwargs):
self.fields = kwargs.pop('fields')
fields = self.fields
super(OrderForm, self).__init__(*args, **kwargs)
if not isinstance(fields, str):
for i in fields.fields.all():
widget = forms.TextInput()
_type = forms.CharField
if i.field_type == Field.TEXTAREA_FIELD:
widget = forms.Textarea
...
self.fields[i.name] = _type(**fields)
This is supposed to get Database created forms and field data and generate fields accordingly. For example:
Form A has fields:
Name (Regular Text Field)
Address (Textarea)
The above code will then generate fields for those.
The next block of code is from my views.py file
FORM_TEMPLATES = {
"0": 'order/details.html',
"1": 'order/details.html',
"2": 'order/details.html',
"3": 'order/details.html',
"4": 'order/details.html',
"confirm": 'order/confirm.html',
}
class Order(SessionWizardView):
form_list = [OrderForm]
def get_current_step_form(self, company, *args, **kwargs):
step_form = [Form.objects.all()]
step_form.append('Confirm')
return step_form
def get_context_data(self, form, **kwargs):
context = super(Order, self).get_context_data(form=form, **kwargs)
# Returns {}, but I want this to return all previous field values
context.update({
'all_data': self.get_all_cleaned_data(),
})
return context
def post(self, *args, **kwargs):
go_to_step = self.request.POST.get('wizard_goto_step', None)
form = self.get_form(data=self.request.POST)
current_index = self.get_step_index(self.steps.current)
goto_index = self.get_step_index(go_to_step)
if current_index > goto_index:
self.storage.set_step_data(self.steps.current,
self.process_step(form))
self.storage.set_step_files(self.steps.current,
self.process_step_files(form))
return super(Order, self).post(*args, **kwargs)
def get_form(self, step=None, data=None, files=None):
"""
Get the form and add to form_list
"""
form = super(Order, self).get_form(step, data, files)
company = ...
get_forms = self.get_current_step_form(company=company)
form_list_value = dict(self.form_list)['0']
while len(self.form_list.items()) < len(get_forms):
self.form_list.update({str(len(self.form_list.items())): form_list_value})
return form
def done(self, form_list, **kwargs):
return HttpResponse("View")
done() is a work in progress, but it doesn't even seem to reach that point, as it keeps going from (for example) Form 0-1-2-3-0-...
The confirm form will not have any field values form the previous pages and will only return {}
Any help would be appreciated,
Thanks
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
I'm trying to get the request.user into a ModelForm. I feel like I've tried all permutations of this from overloading the
__init__
argument (per Django form, request.post and initial) to trying to pass it as a kwargs (per Django form __init__() got multiple values for keyword argument). I
It does seem like the kwargs is the best approach but I'm totally stymied by it.
Here's the ModelForm:
class DistListForm(ModelForm):
members = ModelMultipleChoiceField(queryset=Company.objects.none())
class Meta:
model = DistList
fields = ['name', 'description', 'members', 'is_private']
def __init__(self, *args, **kwargs):
super(DistListForm, self).__init__(*args, **kwargs)
user = kwargs.pop('user', None)
up = UserProfile.objects.get(user=user)
/.. etc ../
Here's how the create function currently works:
def distlistcreate(request):
user = {'user': request.user}
form = DistListForm(**user)
if request.method == 'POST':
form = DistListForm(request.POST)
if form.is_valid():
distlist = form.save(commit=False)
distlist.creator = request.user
distlist.save()
form.save_m2m()
return HttpResponseRedirect(reverse('distlistsmy'))
return render(request, 'distlistcreate.html',{'form':form})
which throws a TypeError: init() got an unexpected keyword argument 'user'. The update method is equally unhelpful:
def distlistupdate(request, object_id):
distlist = get_object_or_404(DistList, id=object_id)
form = DistListForm(user=request.user, instance=distlist)
if request.method == 'POST':
form = DistListForm(request.POST, user=request.user)
It also throws the same error.
I've been banging my head against this wall for two hours now. What is the correct way to pass a keyword argument into a ModelForm?
This is Django 1.6.1 if that makes a difference.
You have to pop the user argument before call super() so it will no conflict wit the default arguments of ModelForm
class DistListForm(ModelForm):
members = ModelMultipleChoiceField(queryset=Company.objects.none())
class Meta:
model = DistList
fields = ['name', 'description', 'members', 'is_private']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(DistListForm, self).__init__(*args, **kwargs)
user_profile = UserProfile.objects.get(user=user)
Just did exactly this yesterday, on Django 1.5, and I am able to do:
def __init__(self, user, *args, **kwargs):
on my ModelForm. Then I just use user without having to pop it from the kwargs.
So, I have the following form:
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
My view:
d = Design.object.get(slug=fromInput)
....
DesignItemInlineFormSet = inlineformset_factory(Design, DesignItem, fk_name="design", form=DesignItemForm,)
if request.method == "POST":
formset = DesignItemInlineFormSet(request.POST, request.FILES, instance=d)
if formset.is_valid():
formset.save()
DesignItemInlineFormSet(instance=d)
As you can tell, in my form, I overwrote the quantity field to be a drop down instead of an integer field.
For some reason, when I submit the form, the data is not updated in the database. However, if I change the form to the following, it works (of course, it doesn't have the dropdowns I want, but it posts to the db). Why is this, and how do I fix it?
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
# CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
# self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
EDIT: Here is the DesignItem model:
class DesignItem(models.Model):
"""Specifies how many of an item are in a design."""
design = models.ForeignKey(Design, related_name="items")
quantity = models.PositiveIntegerField(default=1)
trackable = models.ForeignKey(Trackable, related_name="used")
have you tried just overriding the widget instead of the whole field?
i guess you want a select widget
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'].widget = forms.Select(choices=CHOICES)