unable to save multiple values via Django Form - django

The post request is sending multiple values with same key e.g. values=foo&values=bar
I am seeing only one value in request object in both Django view and form. Not sure what I need to do to get multiple values in Django request object.
// model
class AttributeInstance(models.Model):
somefilter = models.CharField(max_length=255, blank=True)
values = models.TextField()
//form
class ABCModelForm(forms.ModelForm):
class Meta:
model = ABCModel
fields = ('somefilter', 'value')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.data:
self.fields['values'] = forms.MultipleChoiceField(())
// view
class ABCModelView(FormView):
def get(self, request):
form = ABCModelForm()
return render(self.request, 'core/abc_model_edit.html', {'form': form})
def post(self, request):
try:
form = ABCModelForm(request.POST)
form.save()
form = ABCModelForm()
return render(self.request, 'core/abc_model_edit.html', {'form': form})
except Exception as e:
return HttpResponse(status='400')
<!-- HTML -->
<!-- fills the multiple choice field on runtime based on somefilter -->
<!-- the multiple choice UI element looks like below after rendering -->
<form method="post" id="abcModelForm" novalidate="">
<input type="hidden" name="csrfmiddlewaretoken" value="abcdcdcd">
<table>
<tbody>
<tr>
<th><label for="id_somefilter">Description:</label></th>
<td><input type="text" name="somefilter" maxlength="255" id="id_somefilter"></td>
</tr>
<tr>
<th>
<label for="id_values">Values:</label>
</th>
<td>
<select name="values" required="" id="id_values" multiple="multiple">
<option value="dodo">dodo</option>
<option value="bobo">bobo</option>
<option value="foo">foo</option>
<option value="bar">bar</option>
</select>
</td>
</tr>
</tbody>
</table>
<button type="submit">Save</button>
</form>

Its frustrating to know that Django doesn't seem to handle multiple values in form data gracefully.
I used Intellij debugger to inspect request.POST. I could see multiple values in a list for key values but QueryDict as the name suggest seems to understand that there can be only one value for a key and drops the remaining values. For now I have added following hack but not very happy with it. Still looking for a cleaner solution.
payload = dict(request.POST)
payload = {
key: (value if key == 'values' else value[0])
for key, value in payload.items()
}
queryDict = QueryDict(json.dumps(payload))
form = AttributeInstanceForm(queryDict)
// this is how payload looks after conversion from QueryDict
{
'description': ['hmm'],
'values': ['foo', 'bar', 'gogo']
}
I am using Content-Type: application/x-www-form-urlencoded

Related

How to create a form for dealing with multiple django model objects

So I have a model Post with the following definition:
class Post(models.Model):
post_body = models.TextField()
user = models.ForeignKey(
to=User,
on_delete=models.CASCADE
)
published = models.BooleanField(
default=False
)
schedule = models.DateTimeField(
default=timezone.now
)
I have created a form in HTML template allowing the user to select the posts he want to send to another website or social media account. Like so:
<form>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Select</th>
<th scope="col">Post Body</th>
<th scope="col">Published?</th>
<th scope="col">Schedule (Editable)</th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr>
<td>
<div class="form-group">
<input type="checkbox" value="{{ post.id }}">
</div>
</td>
<td>{{ post.post_body }}</td>
<td>{{ post.published }}</td>
<td>
<div class="form-group">
<input type="datetime-local" class="form-control" name="schedule" value="{{ post.schedule|date:"Y-m-d\TH:i" }}">
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-primary" type="submit">Post</button>
</form>
I want to create a Form in the forms.py in order to process this information (i.e. know which all posts were selected) and then perform further actions (calling other APIs in order to publish these to other sites), also since the field schedule is editable, I want to be able to save that as well.
Please help as I cannot think of a possible form for this.
One way to solve it is to use ModelMultipleChoiceField
Here is the structure I've used for similar scenarios:
class SelectPostsForm(forms.Form):
posts = forms.ModelMultipleChoiceField(
queryset=Post.objects.none(), widget=forms.CheckboxSelectMultiple)
def __init__(self, filtered_post_queryset, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['posts'].queryset = filtered_post_queryset
def get_posts(self):
return self.cleaned_data.get('posts')
# Example usage
form = SelectPostsForm(Post.objects.all(), data=request.data)
if form.is_valid():
selected_posts = form.get_posts()
# Process posts...
It will probably work directly with the html code you showed if you add name="posts" the the input.
The "filtered_post_queryset" you can provide via the view if you need to do this on a filtered/restricted queryset. Data will then be properly validated via form.is_valid() against the filtered queryset.
If you want to handle multiple objects of the same type through forms, then you should try with formsets - https://docs.djangoproject.com/en/3.0/topics/forms/formsets/
There's a lot of other options, but I think that formset will cover your needs.
EDIT: Please take it as a guide only:
# forms.py
class PostForm(forms.ModelForm):
is_selected = forms.BooleanField(default=False)
class Meta:
model = Post
fields = ("is_selected", "other", "model" "fields")
PostFormSet = modelformset_factory(Post, form=PostForm)
# views.py
class PostView(generic.View):
def get(self, request, *args, **kwargs):
posts = Post.objects.all() # get your Post queryset
formset = PostFormSet(queryset=posts)
# ...
return response
def post(self, request, *args, **kwargs):
formset = PostFormSet(request.POST)
for form in formset.forms:
if form.is_valid() and form.cleaned_data.get("is_selected"):
# here you can save your instance and/or write logic
# to process gathered information
# ...
return response
You want all your "posts" to render by "one form()" then manipulate the selected posts. Formsets are used for this in django. Although there are many types but any simple formset will get your work done.
Please visit django documentation for implementation: https://docs.djangoproject.com/en/3.0/topics/forms/formsets/
For quick and easy understanding visit this:
https://www.geeksforgeeks.org/django-formsets/

Creating multiple Django model forms to change/edit multiple existing objects on the same page without passing object-id via URL

I'm trying to build an Object detection service where the user can choose multiple objects he would like to detect. The Object Model looks like the following:
#models.py
class Object(models.Model):
name = models.CharField(max_length=50)
image = models.ImageField()
detect = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('Objectdetector:objectdetail', kwargs={'pk': self.pk})
def __str__(self):
return self.name
The page is showing an overview of all the objects a user has created and there is a checkbox next to each object linked to the "detect" attribute. The model form looks like this:
#forms.py
class DetectForm(forms.ModelForm):
class Meta:
model = Object
fields = ['detect']
So in the views.py I'm trying to stick to the Django documentation and pass the instance to the form like such:
form = DetectForm(instance = objectinstance)
but i can't figure out how I am supposed to do it correctly. This is the view, you can ignore the post method for now:
class DetectView(generic.ListView):
template_name = 'Objectdetector/detect.html'
def get(self,request):
objectinstance = #Please help
form = DetectForm(instance = objectinstance)
objs = Object.objects.all()
args = {'form':form, 'all_obj': objs}
return render(request, self.template_name, args)
def post(self, request):
form = DetectForm(request.POST)
if form.is_valid():
form.save()
return redirect('Objectdetector:detectobjects')
args = {'form':form}
return render(request, self.template_name, args)
I then want to loop through all objects and display the images and names linked to them (already working) and display the checkbox next to each object. When the user checks the box of an object and hits submit, the detect attribute should be updated to true. Unfortunately I can only loop through the objects and display a checkbox, but when I hit submit, a new object is created. The table in the detect.html file looks like this:
#detect.html
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Image</th>
<th>Select</th>
</tr>
</thead>
<tbody>
{% for object in all_obj %}
<tr>
<td>
{{ object.name }}
</td>
<td>
<img src="{{ object.image.url }}" height="100">
</td>
<td>
<form method = "post" >
{% csrf_token %}
{{ object.id }}
{{form.as_p}}
<button type="submit">CONTINUE</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</tbody>
</table>
Everything else is working fine, I just don't know how to create and show the checkboxes with the right instance.
I'm pretty sure that I'm missing something very small here due to my lack of Django-knowledge. I would be so happy if someone could help me out!
Thank You so much.

django field is required validators

i have some forms at one page and I try to validate some fields too.
So if I enter wrong input at the test field, I get obviously the message ' invalid input', but also for each other field the messages 'This field is required.'.
How could I fix it? Override the clean function? But how?
class Example1Form(forms.ModelForm):
test = forms.CharField(max_length=30, validators=[RegexValidator(r'^[a-zA-Z0-9_-]+$'), MaxLengthValidator(30)])
And I also distinguish the different forms like:
class View(TemplateView):
def post(self, request, *args, **kwargs):
form_example1 = Example1Form(request.POST)
form_example2 = Example2Form(request.POST)
if form_example1.is_valid():
....
form_example2 = Example2Form()
return render(request, self.template, {'form_example1': form_example1, 'form_example2': form_example2})
if form_example2.is_valid():
....
Thank you very much for help!
First, you need a way to tell which form was submitted. You could name your submit buttons, for example:
<form>
{{ form_example1 }}
<input name="form_example_1" type="submit" value="Submit form 1">
</form>
<form>
{{ form_example2 }}
<input name="form_example_2" type="submit" value="Submit form 2">
</form>
Then, in your view, you should only bind your form to the POST data if that form was submitted. For example:
if "form_example1" in request.POST:
form_example1 = Example1Form(request.POST)
else:
form_example1 = Example1Form()
if "form_example2" in request.POST:
form_example2 = Example2Form(request.POST)
else:
form_example2 = Example2Form()

Data from POST is being ignored by Django

I'm having trouble with a form related issue where the data from a POST is clearly being sent back, but Django is stripping it out so that it won't go into my form class.
Here are some code snippets from my class's post() that show the data is being returned from the template and then how the data is not making it through the form class filtering.
Why is this happening?
What is the harm with me taking the data directly from the POST and not using the built-in form class filtering?
print(request.POST)
Yields this:
<QueryDict: {'book': ['Zippity do dah,'], 'weather': ["It's raining!"], 'dividend': ['Zippity aye!'], 'earnings': ['Yo man, one step at a time.'], 'csrfmiddlewaretoken': ['3MWFOwebVeYfCum8JaGvITRB542b3jbp']}>
and
main_form = self.main_form_class(request.POST)
print('\n','main_form: ',main_form)
Yields this:
main_form: <tr><th><label for="id_weather">weather:</label></th><td><input id="id_weather" maxlength="256" name="weather" size="36" type="text" /></td></tr>
<tr><th><label for="id_book">book:</label></th><td><input id="id_book" maxlength="256" name="book" size="36" type="text" /></td></tr>
<tr><th><label for="id_dividend">dividend:</label></th><td><input id="id_dividend" maxlength="256" name="dividend" size="36" type="text" /></td></tr>
<tr><th><label for="id_earnings">earnings:</label></th><td><input id="id_earnings" maxlength="256" name="earnings" size="36" type="text" /></td></tr>
<tr><th><label for="id_csrfmiddlewaretoken">csrfmiddlewaretoken:</label></th><td><input id="id_csrfmiddlewaretoken" maxlength="256" name="csrfmiddlewaretoken" size="36" type="text" /></td></tr>
You can see that no 'values' are being included in main_form.
main_form.is_valid() is never True
main_form.cleaned_data throws an error - if I pull it out in front of main_form.is_valid()
Help!
OK - here is some more of the code:
I have a sub-class that has a form that is not returning data from a POST request. The super-class also has a form and this IS returning data from the same POST request. That is to say that the super-class is requesting user input and there are several sub-classes that extend the super-class and request other data. The super-class always returns the data from it's part of the form, but the subclasses do not.
The subclasses return data in the POST, but the data doesn't make it back to the Django view. This seems like a bug, (but perhaps it's a feature) or another likely scenario is that I'm missing something. However, I cannot find it.
A solution might be to have no super-class and just have a very non-DRY program. Yuck.
I've put in a few print() to terminal commands to look at what is coming back (or not coming back) to verify this.
I have also used my browser's Developer Tools to see that the POST does, in fact, have the necessary data in it that should be returned to the view for processing. However, this data does not make it back to the view.
The sub-class view code looks like this:
class MainView(SidebarBaseView):
main_form_class = MainViewForm
template_name = 'myapp/my_view.html'
def get(self, request, *args, **kwargs):
context = super(MainView, self).get(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form_initial = {}
for detail in context['my_data']: # my_data comes from get_context_data()
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
context = super(MainView, self).post(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form = self.main_form_class(request.POST)
print('\n','main_form',main_form,'\n') #to terminal
if main_form.is_valid():
main_form_data = main_form.cleaned_data
print('\n', 'cleaned data', main_form_data, '\n') #to terminal
for detail in context['my_data']:
field_name = detail.datatype.name
detail.note = main_form_data[field_name]
detail.save()
main_form_initial = {}
for detail in context['my_data']:
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def get_context_data(self, *args, **kwargs):
context = super(CompanyStatsView, self).get_context_data(**kwargs)
...
return context
My form make dynamic fields since I need to send it a variable number of fields eventually:
class MyMainViewForm(forms.Form):
fields = {}
field_names = ['weather','book','dividend','earnings']
def __init__(self, field_names, *args, **kwargs):
super(MyMainViewForm, self).__init__(*args, **kwargs)
for name in field_names:
self.fields[name] = forms.CharField(label=name, max_length=256, required=False, widget=forms.TextInput(attrs={'size':'36'}))
My template looks like this:
{% extends 'my_sidebar.html' %}
{% block content_right %}
<div class="container-fluid">
<h2>Table</h2>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
{% for detail in my_data %}
<tr height="50">
<td>{{ detail.datatype.full_name }}:</td>
<td class="text-right">{{ detail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
<form action="." method="post"> {% csrf_token %}
{% for note in main_form %}
<tr height="50">
<td>{{ note }}</td>
</tr>
{% endfor %}
<p><input type="submit" value="Save"></p>
</form>
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
Any ideas?
The pertinent part of urls.py is:
from django.conf.urls import url
from . import views
app_name = 'my_app'
urlpatterns = [
url(r'^(?P<slug>[A-Z]+)/data/$', views.MainView.as_view(), name='main-view',),
...
]

django: How to use ModelForm with dynamic 0-n possibilities?

The Quetion
Hello. I'm trying to figure out the best way to use one form to create one parent object and then create 0-n sub objects. I'm assuming ModelForms are what I need to do, but I'm having a hard time understanding how to structure the template and view. Would someone be willing to explain how to create n number of sub objects?
The Research
I've read several other articles and posts relating to this:
django model/modelForm - How to get dynamic choices in choiceField?
https://stackoverflow.com/questions/5575560/how-do-i-create-a-drop-down-menu-in-django-using-a-modelform-with-dynamic-values
http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/
Overriding the save method in Django ModelForm
To name a few.
The Details
I have two models like so:
// models.py
class DataItem(models.Model):
name = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
date_last_updated = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, blank=False)
def __unicode__(self):
return self.name
class DataItemSet(models.Model):
item = models.ForeignKey(DataItem, blank=False)
type_ind = models.IntegerField()
And I've created two ModelForms to correspond:
// forms.py
class CreateDataItemForm(forms.ModelForm):
class Meta:
model = DataItem
exclude = ('owner',)
def save(self, user, commit=True):
item = super(CreateDataItemForm,self).save(commit=False)
item.owner = user
if commit:
item.save()
return item
class CreateDataItemSetForm(forms.ModelForm):
class Meta:
model = DataItemSet
exclude = ('item',)
def save(self, parent, commit=True):
set = super(CreateDataItemSetForm,self).save(commit=False)
set.item = parent
if commit:
set.save()
return set
And in my view, I'm trying to have one form submit the creation of a new DataItem and 1-n DataItemSets which are owned by the DataItem. Here is an example of the output of the form:
And the template:
<form action="." method="post">
{% csrf_token %}
<table>
{{ create_form.as_table }}
</table>
<table>
<tr>
<th>What to track:</th>
<td>
<select>
<option value="1">Number</option>
<option value="2">Currency ($)</option>
<option value="3">Date</option>
<option value="4">Day</option>
<option value="5">Time</option>
</select>
</td>
<td>
</td>
</tr>
<tr>
<th>What to track:</th>
<td>
<select>
<option value="1">Number</option>
<option value="2">Currency ($)</option>
<option value="3">Date</option>
<option value="4">Day</option>
<option value="5">Time</option>
</select>
</td>
<td>
<button type="button">+</button>
</td>
</tr>
</table>
<p>
<button type="submit">Create</button>
</p>
</form>
And lastly, the view:
// views.py
#login_required
#csrf_protect
def create_data_item(request):
create_form = CreateDataItemForm()
c = {'create_form':create_form}
c.update(csrf(request))
if request.method == 'POST':
data = request.POST.copy()
form = CreateDataItemForm(data, instance=DataItem())
item_sets = [CreateDataItemSetForm(request.POST, prefix=str(x), instance=DataItemSet()) for x in range(0,9)]
if form.is_valid():
# create new data item
new_item = form.save(request.user)
#create new set of stuff to track
for item_set in item_sets:
new_item_set = item_set.save(new_item)
# return to the add entry page
return redirect('/add')
else:
return render_to_response('internal/create_data_item.html',c)
I'm missing how to allow for dynamic extraction of 0-n DataItemSets from the form. I can't figure it out with ModelForm.
Thanks for any help!
This is what model formsets - specifically, inline formsets - are for.