So I have two forms, Sale and SaleItems
SaleForm = SaleForm(request.POST or None, auto_id=False, prefix = 'SaleForm')
SaleItemsForm = modelformset_factory(
Sale, form = SaleItemsForm, formset = ItemsFormSet, extra=1, can_delete=True
)
once they're both given POST data and valid, they're in the usual statement:
if SaleForm.is_valid() and SaleItemsForm.is_valid():
pass
When it comes time to do validation I've superseded the basemodelformset and want to write my own custom clean method for the modelformset. I want to use cleaned data from SaleForm inside the clean method for the ItemsFormSet:
from django import forms
class ItemsFormSet(forms.models.BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(ItemsFormSet, self).__init__(*args, **kwargs)
def clean(self):
super().clean()
print(TheSaleForm.cleaned_data['Value'])
This doesn't work, and I've tried a few dumb things:
try to make SaleForm a global and access it between the views.py and forms.py modules. That was a bad idea and now I understand more about the module scope
try to import the actual object, again not smart
I'm assuming there has to be a way to do this without saving the cleaned data off somewhere to the database and retrieving it again in the clean method of the formset. I'm not sure if overwriting ItemsFormSet.is_valid() and trying to allow a kwarg dictionary item to be passed through would be the right way to go.... but I'm hoping someone has an idea of what the "correct" way to approach this is.
You should allow the data to be passed into the init of ItemsFormSet and keep it as an instance attribute which you can reference later.
class ItemsFormSet(forms.models.BaseModelFormSet):
def __init__(self, *args, **kwargs):
self.sale_form = kwargs.pop('sale_form', None)
super(ItemsFormSet, self).__init__(*args, **kwargs)
def clean(self):
super().clean()
print(self.sale_form.cleaned_data['Value'])
and now in your view:
if request.method == 'POST':
form = SaleForm(request.POST)
formset = SaleItemsForm(request.POST, sale_form=form)
Related
I'm trying to write use Django FormView and a bit of ingenuity to create a view which will allow me to get inputs from a user that will be fed to a function. I'd like the code to be reusable, so I'd like to make a view that will be able to take a target function as a parameter and automagically create a form appropriate to that function. There is more plumbing to be done, but the general idea would be:
class FormViewForFunction(FormView):
template_name = '...'
func = None
def get_form_class(self):
class _FunctionForm(forms.Form):
pass
a = inspect.getargspec(self.func)
for argname in a['args']:
setattr(_FunctionForm, argname, forms.CharField())
return _FunctionForm
The idea would be that then you could set up something in your URLConf that used FormViewForFunction.as_view(func=***insert any function you want***) and you would wind up being presented with a form that was appropriate for specifying parameters for that function. Let's not worry about what would happen on form submission. For now I'm just stuck getting the form to generate properly.
With the code above, the form doesn't wind up having any fields! What am I doing wrong?
form's fields are initialized during initialization, you should override the __init__ method and then append the fields to the self.fields dictionary
This should work:
class FormViewForFunction(FormView):
template_name = '...'
func = None
def get_form_class(self):
a = inspect.getargspec(self.func)
class _FunctionForm(forms.Form):
def __init__(self, *args, **kwargs):
super(_FunctionForm, self).__init__(*args, **kwargs)
for argname in a['args']:
self.fields[argname] = forms.CharField()
return _FunctionForm
I have a class like so:
class EmailForm(forms.Form):
users = forms.MultipleChoiceField(required=False, widget=MultipleHiddenInput())
subject = forms.CharField(max_length=100)
message = forms.Textarea()
def __init__(self, users, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.users.choices = users
# self.fields['users'].choices = []
The commented line at the bottom works perfectly if I use it instead of self.users.
Am I right in thinking that users, subject and message are class level so that is why they are popped out of the attribute list?
So self.fields is the per object copy of the attributes in case I want to change them in some way?
Thanks.
The Form class uses the DeclarativeFieldsMetaclass, which enables the declarative syntax for the fields.
The implementation means that the form class and instance does not actually have an attribute self.field_name for each field. That is why trying to use self.users gives an error.
The fields of the form instance can be accessed as self.fields, which is created when you call super in the __init__ method.
The fields of the form class can be accessed as self.base_fields.
I have Chart and Module models (see code below). Each chart belongs to a module (via a ForeignKey). A chart may have another chart as its parent (another ForeignKey). What I would like in the admin is that the dropdown for parent on a particular chart only includes charts in the same module.
I'm looking for a Python solution, not AJAX. That means that on creating a new chart the dropdown will have to be empty (without a model instance there's no module selected) and that changing the module in the admin won't update the parent options to match until the model is saved. I'm ok with that.
There are plenty of similar-sounding questions (and answers) that turn out to filter the options according to the user; ModelAdmin.formfield_for_foreignkey gets the request as an argument so you can use request.user, but it doesn't get given a model instance to play with. (I'm using Django 1.3.)
My models (highly simplified of course):
class Module(models.Model):
pass
class Chart(models.Model):
module = models.ForeignKey(Module)
parent = models.ForeignKey(Chart, blank=True, null=True)
class ChartAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# I would like to do this, but have no "instance":
kwargs['queryset'] = Chart.objects.filter(module=instance.module)
return super(ChartAdmin, self).formfield_for_foreignkey(self, db_field, request, **kwargs)
admin.site.register(Chart, ChartAdmin)
Just override the ModelForm being used:
class ChartAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ChartAdminForm, self).__init__(*args, **kwargs)
if self.instance.module_id:
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(module=self.instance.module)
class ChartAdmin(admin.ModelAdmin):
form = ChartAdminForm
...
From what i've seen in the source code, kwargs should contain only the widget (not helping !).
One possible hack, would be to override the get_form() modeladmin method, just to set request.current_object. Then, you could use request.current_object in formfield_callback:
def get_form(self, request, obj=None, **kwargs):
request.current_object = obj
return super(ChartAdmin, self).get_form(request, obj, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
instance = request.current_object
kwargs['queryset'] = Chart.objects.filter(module=instance.module)
return super(ChartAdmin, self).formfield_for_foreignkey(self, db_field, request, **kwargs)
I have one model and I've created a form out of the model using ModelForm. Now, I want to spread the form across two pages. For example, the first three fields will appear on the first page then the user clicks next and the last three fields appear on the second page. Then he clicks submit and the user submitted data is added to the database.
I took a look at the docs for the Form Wizard and it seems like it would work for model forms as well? Can someone confirm this?
And if it does, can someone explain the process of creating a WizardView class.
This example is given in the docs and I don't understand what the second two parameters are. Is form_list just a list of form objects that you've instantiated based on your form classes? And what is **kwargs?
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
Thanks in advance for your help!
Say your model has two fields
class AModel( Model ):
fieldA = CharField()
fieldB = CharField()
We want to set each field in a separate step using a FormWizard. So we create two ModelForms, each showing one field:
class Form1( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldA', )
class Form2( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldB', )
We call our form wizard AWizard; the url.py entry should look something like
url( r'^$', AWizard.as_view( [ Form1, Form2 ] ) ),
In the implementation of AWizard we need to make sure all the forms write their data to a single instance, which we then save to the database:
class AWizard( SessionWizardView ):
instance = None
def get_form_instance( self, step ):
if self.instance is None:
self.instance = AModel()
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
Notice that we override the method get_form_instance. This method returns the model instance the forms bind to.
You might think (I did), that this method creates an instance for the first request (the first step of the wizard), and then keeps using that same instance for all steps.
Actually, it's a little more complicated. For each request a new instance of AWizard is created, which in turn creates a new AModel instance. So, the steps don't share a single instance to start with.
The magic happens when the last form is submitted. At this point all forms are revalidated, each form calls get_form_instance and they end up populating a single AModel instance.
That instance is then saved in done.
Form Wizard is being built into Django 1.4 so is a good way to go about this. It should do what you want, but you may need a couple of tweaks.
Don't worry about the kwargs in done() at the moment - you're not going to need them.
form_list is the list of forms that you want to use for your steps - from urls.py
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
)
[ContactForm1, ContactForm2] will be passed to done() as form_list.
What you will need to do is break your ModelForm into separate forms. The easiest way to do this (if you want your model on several forms) is to not use ModelForm but just create your own form. It's pretty easy:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Once your forms reflect the portions of your model, just create the views and patterns as described in the docs and set do_something_with_the_form_data(form_list) to a function that completes your model from the form data and then does a save.
You could use ModelForm but - only if you can persuade it to produce different forms for Form Wizard to use for each step - that's going to be the tricky part.
The view proposed by #wuerg did not work for me, I had to do this:
class AWizard( SessionWizardView ):
def dispatch(self, request, *args, **kwargs):
self.instance = AModel()
return super(ApplyWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance( self, step ):
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect(reverse(thanks))
I had to alter the solution of #wuerg and #madmen to work in my usecase (saving the Model after every step). The big advantage of this approach is that it always uses the same instance of the AModel instead of creating a new instance for every step:
class AWizard(SessionWizardView):
instance = AModel()
def dispatch(self, request, *args, **kwargs):
return super(AWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance(self, step):
return self.instance
def done(self, form_list, **kwargs):
self.save_model()
return render_to_response('done.html')
I was trying to pass an additional parameter to my form, which is anObject to ForeignKey relation. But dunno why form returns __init__() got an unexpected keyword argument 'parent' when I'm pretty sure that it is possible to send additional parameters to form's __init__ (ie here : Simple form not validating). Am I wrong ?
def add_video(request):
parent = ParentObject.objects.all()[0]
if request.method == 'POST':
form = VideoForm(data=request.POST, parent=parent)
if form.is_valid():
form.save()
next = reverse('manage_playforward',)
return HttpResponseRedirect(next)
else:
form = VideoForm()
class VideoForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
try:
self.parent = kwargs.pop['parent']
logging.debug(self.parent)
except:
pass
super(VideoForm, self).__init__(*args, **kwargs)
kwargs.pop['parent'] is throwing TypeError: 'builtin_function_or_method' object is unsubscriptable, because you're trying to do a key lookup on a function method ({}.pop). This error is then being swallowed by your exception handler.
For this to work do kwargs.pop('parent', None). In your case:
class VideoForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent', None)
super(VideoForm, self).__init__(*args, **kwargs)
As a side note, 99% of time its best to only catch specific Exceptions in your except blocks. Doing so will help dodge bugs/confusion like this. Also, I would highly suggest adding unit tests for this custom construction (or just TDDing your other code, but that's a separate issue)