In my django app, a user can create a Document model object by either 1) entering data into a form or 2) copying an existing Document. In the second case, I want to use is_valid to confirm that the existing Document is still valid (validity rules may have changed since its creation). I have this working like so:
doc = original_doc.clone()
form = DocumentForm(data=doc.__dict__, instance = doc)
if form.is_valid():
# doc is valid
else:
... do stuff ...
Now I need to do the same for an inlineformset. Each Document can have 1 or more Item, and I use inline formset for cross-field validation that depends on both a Document field and an Item field. I have this:
ItemFormSet = inlineformset_factory(Document,
Item, form=ItemForm, formset=CustomItemFormSet)
item_formset = ItemFormSet(data= XXX, instance = doc,
queryset=doc.item_set, doc_form=form)
if item_formset.is_valid():
# item_formset is valid
else:
... do stuff...
My question is: how do I obtain values for the data parameter (XXX) in the ItemFormSet call? When I display a form to the user, I can use request.POST. Without that, do I need to manually reconstruct the dictionary field by field? It gets complicated due to the data that formsets prepend to the field names, like: 'id_item_set-0-field_name'.
My apologies if my question was not straightforward, but I did find a way forward, and will post here in case others find it helpful.
Basically, as far as I found, YES, I do need to manually reconstruct the dictionary field to pass to the ItemFormSet constructor, to create the management form data. Given the single 'item' variable that I wanted to test for validation, I created a dictionary containing item's fields and then added management form data:
item_dict = item.__dict__
item_dict_itemset = {}
for key, value in item_dict.iteritems():
item_dict_itemset["item_set-0-{}".format(key)] = value
item_dict_itemset["item_set-INITIAL_FORMS"] = 0
item_dict_itemset["item_set-MIN_NUM_FORMS"] = 0
item_dict_itemset["item_set-MAX_NUM_FORMS"] = 1000
item_dict_itemset["item_set-TOTAL_FORMS"] = 1
item_formset = ItemFormSet(data-item_dict_itemset, instance=doc, queryset=doc.item_set)
formset_is_valid = item_formset.is_valid()
Related
i am building an application that has a model with three fields Company,Name, position. in the same model i want to have company name as one field while the user can add name and positions for multiple candidates. the reason am trying to do that is because i didnt find any proper way to set automatically select the foreign key based on the company name entered since foreign key is a drop down list and couldnt figure out the way to make foreign key field equal to company name entered.
appreciate help and suggestions if any for the approach i have in mind.
You need two forms (or more usefully one form and one formset). Use form prefixes to make them distinguishable. Pass both to the template, say as selectform and candidate_formset and in the template, use
{{selectform}}
{{candidate_formset}}
The first is a company-select form. It might, for example, be
class CompanySelectForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['company']
The second is a form, or probably a formset, for entering one, or (via a formset) as many candidates as there are to be entered. It will look like
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['name','position']
Now, you use commit=False (docs) to create objects but not save them. First, process CandidateSelectForm, which will give you a Candidate object with a valid company instance, but not save it. Then process the formset of CandidateForm, again with commit=False, which will give you a list of candidate instances with no company, again unsaved. Finally for each candidate in this list, set the company field of every candidate to the one on the object retrieved by CandidateSelectForm and save it.
It will probably be easier to write a plain view function, than messing around with method overrides trying to get the class-based views to process two forms this way.
Edit - added on request.
The view could be modelled on this one in the Django doc. I've made the obvious changes in line with the earlier part of the answer, but it's probably full of errors and I'm not going to debug it further here
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create form instances and populate with data from the request:
cs_form = CompanySelectForm(request.POST, prefix="cs")
cand_form = CandidateSelectForm( request.POST, prefix="cand")
# check whether it's valid:
if cs_form.is_valid() and cand_form.is_valid():
selector = cs_form.save(commit=False)
candidate = cand_form(commit=False)
candidate.company = selector.company
candidate.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
cs_form = CompanySelectForm( prefix='cs')
cand_form = CandidateSelectForm( prefix='cand')
return render(request, 'name.html', {
"select_form": cs_form,
"candidate_form": cand_form,
})
Once you have got this working for a single candidate, you can progress to turning candidate_form into a formset, documented here which will let you enter any number of candidates to be attached to the one selected company.
I have a ModelForm field that is based on the following Model:
class Phrase(models.Model):
subject = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
object = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
The modelform (PhraseForm) has a field 'subject' that is a CharField. I want users to be able to enter a string. When the modelform is saved, and the string does not match an existing Entity, a new Entity is created.
This is why I had to overwrite the "subject" field of the Modelform, as I cannot use the automatically generated "subject" field of the Modelform (I hope I'm making myself clear here).
Now, all tests run fine when creating a new Phrase through the modelform. But, when modifying a Phrase:
p = Phrase.objects.latest()
pf = PhraseForm({'subject': 'anewsubject'}, instance=p).
pf.is_valid() returns False. The error I get is that "object" cannot be None. This makes sense, as indeed, the object field was not filled in.
What would be the best way to handle this? I could of course check if an instance is provided in the init() function of the PhraseForm, and then assign the missing field values from the instance passed. This doesn't feel as if it's the right way though, so, is there a less cumbersome way of making sure the instance's data is passed on through the ModelForm?
Now that I'm typing this, I guess there isn't, as the underlying model fields are being overwritten, meaning the form field values need to be filled in again in order for everything to work fine. Which makes me rephrase my question: is the way I've handled allowing users to enter free text and linking this to either a new or existing Entity the correct way of doing this?
Thanks in advance!
Why are you modifying using the form.
p = Phrase.objects.latest()
p.subject = Entity.objects.get_or_create(name='anewsubject')[0]
docs for get_or_create
If you are actually using the form it should work fine:
def mod_phrase(request, phrase_id=None):
phrase = get_object_or_404(Phrase, pk=phrase_id)
if request.method == 'POST':
form = PhraseForm(request.POST, instance=phrase)
if form.is_valid():
form.save()
return HttpResponse("Success")
else:
form = PhraseForm(instance=phrase)
context = { 'form': form }
return render_to_response('modify-phrase.html', context,
context_instance=RequestContext(request))
Setting the instance for the ModelForm sets initial data, and also lets the form know which object the form is working with. The way you are trying to use the form, you are passing an invalid data dictionary (lacks object), which the form is correctly telling you isn't valid. When you set the data to request.POST in the example above, the request.POST includes the initial data which allows the form to validate.
I'm wondering if there is a simple way of creating a "duplicate" ModelForm in Django - i.e. a form that is prefilled with the content of an existing model instance (excepting certain fields, such as those that are unique), but creates a new instance when saved.
I was thinking along the lines of supplying an instance to a ModelForm so that the data is prefilled as with an "edit" form, then setting the instance to None before saving, but this gives a "'NoneType' object has no attribute 'pk'" error when calling .save() on the form. It seems the act of supplying an instance when constructing the form creates some dependency on it being there at the end.
I have had trouble finding a solution to this problem, but I can't imagine a "duplicate" form being too unique, so maybe I am missing something simple?
Any help would be appreciated.
I think what you need is a way to fill in the initial values for the fields in the form. The best way to accomplish this would be to create a dictionary of initial values (keyed by field name) from an existing instance and supply this to the form.
Something like this:
class AddressForm(forms.ModelForm):
class Meta:
model = Address
# Inside view:
address = Address.object.get(**conditions)
initial = dict()
for field in ('state', 'zipcode'): # Assuming these are the fields you want to pre-fill
initial[field] = getattr(address, field)
form = AddressForm(initial = initial)
class AddressForm(forms.ModelForm):
class Meta:
model = Address
# Inside view:
address = Address.object.get(pk=<your-id>)
address.pk = None # that's the trick, after form save new object will be created
form = AddressForm(instance=address)
I have a simple Django Form:
class testForm(forms.Form):
list = forms.CharField()
def getItems(self):
#How do I do this? Access the data stored in list.
return self.list.split(",") #This doesn't work
The list form field stores a csv data value. From an external instance of testForm in a view, I want to be able to look at the .csv value list stored in the form field.
Like others have already mentioned, you need to make use of the form's cleaned_data dictionary attribute and the is_valid method. So you can do something like this:
def getItems(self):
if not self.is_valid():
return [] # assuming you want to return an empty list here
return self.cleaned_data['list'].split(',')
The reason your method does not work is that the form fields are not your typical instance variables. Hope this helps!
What you usually do in django in a view to get the form data would be something like this.
form = testForm(request.POST)
if form.is_valid:
# form will now have the data in form.cleaned_data
...
else:
# Handle validation error
...
If you want to do some data formatting or validation yourself you can put this in the validation method in the form. Either for the entire form or for a form field. This is also a great way to make your code more DRY.
There are a couple of things you need to know here.
First is that generally in a Python class method you access the attributes through the 'self' object. So in theory your function should be:
def get_items(self):
return self.list.split(",")
However, in the case of a Django form, this won't work. This is because a field doesn't have a value of its own - the value is only attached to the field when it's rendered, and is obtained in different ways depending on whether the value was applied through initial data or by passing in a data dictionary.
If you have validated the form (through form.is_valid()), you can get the form via the cleaned_data dictionary:
return self.cleaned_data['list']
However this will fail if list has failed validation for any reason.
Call is_valid on the form, then access the cleaned_data dictionary.
I get data in from POST and validate it via this standard snippet:
entry_formset = EntryFormSet(request.POST, request.FILES, prefix='entries')
if entry_formset.is_valid():
....
The EntryFormSet modelform overrides a foreign key field widget to present a text field. That way, the user can enter an existing key (suggested via an Ajax live search), or enter a new key, which will be seamlessly added.
I use this try-except block to test if the object exists already, and if it doesn't, I add it.
entity_name = request.POST['entries-0-entity']
try:
entity = Entity.objects.get(name=entity_name)
except Entity.DoesNotExist:
entity = Entity(name=entity_name)
entity.slug = slugify(entity.name)
entity.save()
However, I now need to get that entity back into the entry_formset. It thinks that entries-0-entity is a string (that's how it came in); how can I directly access that value of the entry_formset and get it to take the object reference instead?
I would suggest writing a helper factory function for your form set so that you can customize the display widget according to the data. Something like the following:
def make_entry_formset(initial_obj=None, custom_widget=forms.Textarea):
# these will be passed as keyword arguments to the ModelChoiceField
field_kwargs={'widget': custom_widget,
'queryset': Entity.objects.all()}
if initial_obj is not None:
field_kwargs.update({'initial': initial_obj})
class _EntryForm(forms.ModelForm):
entity = forms.ModelChoiceField(**field_kwargs)
class Meta:
model = Entry
return modelformset_factory(Entry, form=_EntryForm)
Then in your view code you can specify the widget you want and whether to bind to an initial Entity object. For the initial rendering of the formset, where you just want a Textarea widget and no initial choice, you can use this:
formset_class = make_entry_formset(custom_widget=forms.Textarea)
entry_formset = formset_class()
Then if you want to render it again (after the is_valid() block) with the Entity object already defined, you can use this:
formset_class = make_entry_formset(initial_obj=entity,
custom_widget=forms.HiddenInput)
entry_formset = formset_class(request.POST, request.FILES)
You can use any widget you like, of course, but using a HiddenInput would prevent the end user from interacting with this field (which you seem to want to bind to the entity variable you looked up).