Muliple instances of modelform - django

I have a list in my template. For each item in the list, I have a {{ modelform }} that contains a checkbox. I can check the box and it updates as should. The problem is that when I check the box for one item and submit, it submits for all of the checkboxes because they are the same in each instance. Is there a way to set up a unique checkbox instance for each item in the list?
Current each modelform checkbox renders the same like this:
<input name="is_solution" type="checkbox" class="is_solution" id="is_solution">
I also tried using
test = request.POST.get('checkbox')
and
test = request.POST.get('checkbox')
thinking that using this I might be able to post an update in my view. I think I am going about this all wrong and I am lost. Essentially, I would like to have a checkbox on a list much like here on stackexchange where you can confirm an answer. Any suggestions?

You have to use form's prefix in the view like (just something unique for each form object):
def foo(request, ...):
objs = Model.objects.filter(...)
forms = []
for i, obj in enumerate(objs):
form = ModelForm(instance=obj, prefix=str(i))
forms.append(form)
...
This will make sure each form has unique identifier, hence you will be able to submit a specific form.
And you can render the forms like usual in the template:
<form ...>
{% csrf_token %}
{% for form in forms %}
{{ form }}
{% endfor %}
</form>

Related

How can i add a "like" button in a Django class ListView

I am pulling my hair out trying to add a "like" button in my siteĀ“s post app, but as i want to add it in a ListView that contains the rest of the posts entries and everyone has the option to be commented I have added a Formixin to do so, so, now i cannot add another form for the like button as it would mean two posts requests....so I am not finding a clear solution... I have read here and there about using AJAX or Json techs but as im new programing im kind of stuck in it... has anyone any tip to offer?
While using AJAX (javascript XHR requests) would be the proper way so the page doesn't need to be refreshed when just clicking a like button, you can do it without AJAX.
HTML
On the HTML side of things, you can have multiple forms (<form>), one for each post, which have a hidden input field that's the post's id. You have set that explicitly in the HTML template, e.g.
{% for post in post_list %}
<h3>{{ post.title }}</h3>
<p>{{ post.summary }}</p>
<form method="post">
{% csrf_token %}
<input type="hidden" value="{{ post.id }}" name="{{ form.id.html_name }}">
<input type="submit">Like</input>
</form>
{% endfor %}
So basically you're reusing the form multiple times, changing the "value" attribute to match the post.
Django Form
Adding the FormMixin to your view is the right step, just use the form_class to a custom LikeForm with just one field that's an IntegerField called id.
View
By adding the FormMixin you get the form_valid() method, which you'll want to override to save the like:
def form_valid(self, form):
id = form.cleaned_data['id']
try:
post = Post.objects.get(id=id)
except Post.DoesNotExist:
raise Http404
post.likes.add(self.request.user) # assuming likes is a m2m relation to user
return redirect('post_list') # this list view
Hopefully I am not so late, I had similar challenges trying to implement the same functionalities on my website.
I came to realize that each button id should be unique (Preferably the post id if blog), but the classes can be the same.
I was able to solve it. Here is an article I wrote on medium recently on the steps I followed to so get this working you can check it out here

Wagtail not pulling through custom field panels

I'm overriding the wagtail AbstractFormField panel attribute in the following way:
...
before_input = RichTextField(verbose_name=_('before input'), blank=True)
after_input = RichTextField(verbose_name=_('after input'), blank=True)
panels = [
FieldPanel('label'),
FieldPanel('before_input'),
FieldPanel('after_input'),
FieldPanel('required'),
FieldPanel('field_type', classname="formbuilder-type"),
FieldPanel('choices', classname="formbuilder-choices"),
FieldPanel('default_value', classname="formbuilder-default"),
]
where the other panels are what comes out of the box.
This is working perfectly on the admin side and also saving as rich text into my database
I am pulling this through to my form in my template in the following way:
<form action="{% pageurl page %}" method="POST" class="lm-ls1" id="feedback-form">
{% csrf_token %}
{{ form.question1.help_text }} <!-- Simpler non interable way -->
{{ form.question1.before_input }}
<p>---------------</p>
{% for row in form.fields.values %}
{{row.choices}}
<p>---------------</p>
{{row.help_text}}
<p>---------------</p>
{{row.before_input}}
{% endfor %}
</form>
But I am only getting html output for the form panels excluding the before_input and after_input ones
I am getting through roughly the following:
Overall, how did you feel about the service you received today?
---------------
[('Very satisfied', 'Very satisfied'), ('Satisfied', 'Satisfied'),
('Neither satisfied nor dissatisfied', 'Neither satisfied nor dissatisfied'), ('Dissatisfied', 'Dissatisfied'), ('Very dissatisfied', 'Very dissatisfied')]
---------------
Overall, how did you feel about the service you received today?
---------------
---------------
How can I access the before_input field panel data stored in the _formfield wagtail table?
Bit late but hopefully this still helps you or someone else out there.
How Wagtail Forms Work
Wagtail forms provided to the view context for AbstractFormPage models is a fully instanced Django Form. This means that you will only ever find values in the form that can be given to a Django Form.
This includes fields, which are instances of Django's Fields (eg. CharField) and there is no simple way to add additional attributes to these fields.
You can see how the Form object is built in the Wagtail FormBuilder class definition.
1 - Make a Custom Template Tag
A somewhat simple way to get additional attributes on your FormField (Wagtail's FormField) is using a template tag.
Create a new file in in a folder templatetags in your app, and build a simple_tag that will take the form_page, the field (which will be a Django Field instance) and a string of the attribute name you want to get.
# myapp/templatetags/form_tags.py
from django import template
from django.utils.html import mark_safe
register = template.Library()
#register.simple_tag(name='form_field_attribute')
def form_field_attribute(form_page, field, attribute_name, default=None):
"""Return attribute on FormField where field matches 'field' provided."""
# field is a django Field instance
field_name = field.name
results = [
# if html is stored, need to use mark_safe - be careful though.
mark_safe(getattr(form_field, attribute_name, default))
# get_form_fields() is a built in function on AbstractFormPage
for form_field in form_page.get_form_fields()
# clean_name is property on AbstractFormField used for Django Field name
if form_field.clean_name == field_name]
if results:
return results[0]
return default
2 - Revise your form_page.html Template
In your template, cycle through your form (this is the Django Form instance) and use the template helper to get you the extra attributes you need. Example below, passing in page or self will work the same as they are both the instance of your FormPage.
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
{% for field in form %}
<div>{% form_field_attribute page field 'before_input' %}</div>
{{ field }}
<div>{% form_field_attribute page field 'after_input' %}</div>
{% endfor %}
<input type="submit">
</form>

dynamic form in Flask

in a movie rating app,I would like to generate a WTF form in flask with dynamic number of fields. i.e, if there are three movies, there will be three fields.
I thought about a few options, but none of them worked:
class RatingForm(Form):
rating = TextField("rating",[validators.Length(min=1, max=1)])
movie_order=TextField("movie",[validators.Length(min=1, max=1)])
submit = SubmitField("submit rating")
pass a parameter to the form object - I don't see how can I pass a parameter to this kind of class
make a loop inside the template, thus generate and return multiple forms, and choose the correct one. this also doesnt work, since the request.form is immutableDict, and I end up having multiple fields with the same key, which I cant access.
{% for movie in movies_to_rate %}
<p>
<form method="POST" enctype="multipart/form-data" action="/rate">
{{ movie}}
{{ forms[movie].rating}}
{{ forms[movie].submit }}
<input type="submit" value="Go">
</p> {% endfor %}
any ideas about what can I do?
I think you can generate a list of TextField's as a class member instead of using one field object. (Though it looks a bit weird, I assume your validators are what you meant.)
class RatingForm(Form):
def __init__(self, count):
self.ratings = [TextField("rating_" + str(i), [validators.Length(min=1, max=1)])
for i in range(count)]
...

Dynamic Forms (Formsets) in Flask / WTForms?

In Django you have a multiple form feature called Formsets, which you can use to create multiple forms into the same template. I am trying to achieve something similar in Flask / WTforms.
<form action="{{ url_for('request-accept') }}" method='post'>
<table>
<tbody>
{% for request in requests %}
<tr>
<td>
<div class="person-header">
<img src="{{request.profile_pic_url}}" class="img-circle profile-image"/>
<p class="person-header-text">{{request.fullname()}}</p>
</div>
</td>
<td>
<input type="checkbox" id="{{request.key.urlsafe()}}" name="checkbox{{loop.index}}">
</td>
</tr>
{% endfor %}
</tbody>
</table>
<input class='submit btn btn-primary' type=submit value="Connect">
</form>
The idea is having one form that wrapps all the checkboxes, which the user like to tick to become friends with. As it currently stands I am not really generating any form class in Flask, since I don't know how to make a dynamic FormSet, hence I create the form dynamically inside the html.
The caveat is though, I don't know how to retrieve the selected user id via the checkbox. (I have stored it in the id because I didn't know better)
But I can't access the id in request.values['checkbox1']. I can only see if its on or off.
Any suggestions how to solve this please?
The problem
Your problem is that id is not sent back to the server - only value is ... and since your checkboxes don't have a value attribute the default value is used, which happens to be on.
Since you tagged this with wtforms, I'll give you an example of how you could be doing this.
Never have this issue again
The WTForms' documentation has an example class that will create a list of checkboxes for you:
class MultiCheckboxField(SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
You would use this field in your custom form in this manner:
class FriendsForm(Form):
potential_friends = MultiCheckboxField("Friends", coerce=int)
# ... later ...
#app.route("/add-friends", methods=["GET", "POST"])
def add_friends():
form = FriendsForm(request.form)
# lookup friends, for now we'll use a static list
form.potential_friends.choices = [(1, "Sam"), (2, "Joe")]
# We'll also need a mapping of IDs to Person instances
# (Made up for this example - use your own ;-) )
mapping = {
1: Person("Sam", profile_pic="sam.jpg"),
2: Person("Joe", profile_pic="joe.png")
}
if request.method == "POST":
# Mark new friends
return render_template("friends.html", form=form, persons=mapping)
Then, in friends.html you can iterate over the form.potential_friends field:
{% for person in form.potential_friends %}
persons[person.data].profile_pic :: {{person.label}} :: {{person}}<br>
{% endfor %}
You can customize your HTML inside the for loop. My particular example should render (with a few more attributes, like for and name):
sam.jpg :: <label>Sam</label> :: <input type="checkbox" value="1">
joe.png :: <label>Joe</label> :: <input type="checkbox" value="2">
I personnally would add a hidden input field in your fieldset under each checkbox with a name such as "friend_nametag1" and a value corresponding to the friend's ID. With the 1 being incremented for every "friend". You can thus look it up in flask view using something like
friend_list = []
list_of_checkboxes = ... (fetch from request.form... ?)
dict_of_friend_nametags = ... (build from request.form... ?)
if 'checkbox1' in list_of_checkboxes:
friend_list.append(dict_of_friend_nametags.get('friend_nametag1')
Obviously you can use some sort of logic to have an incremental index (the "1" in "checkbox1" in this case).
I'm not too familiar with WTForms so there might be a better way to do this, but this solution is fairly straightforward to implement with your current code.
If you want a FieldSet or FormSet I would suggest you use the FormField in conjunction with the FieldList with the related docs here : http://wtforms.simplecodes.com/docs/dev/fields.html#field-enclosures
p.s.: I would not recommend using request as a variable name in your template or your code as it may shadow the global request of flask ? XD

Update non-request user information in a Django template and view

So I have a ManageUserForm in forms.py-- it renders correctly but it doesn't pull the right data from the user i'm trying to edit.
In the template, I have a for loop that works correctly
{% for tenants in tenants %}
{{ tenants.user }} {{ tenants.type }}
{% endfor %}
This template renders the list of objects in the UserProfile. And it does it correctly. The challenge I face is updating the "tenants.type" attribute. Again, the type shows up correctly but I don't know how to update it from this template page.
#views.py
def manage_users(request):
tenants = UserProfile.objects.all()
form = ManageUserForm(request.POST or None)
if form.is_valid():
update = form.save(commit=False)
update.save()
return render_to_response('manage_users.html', locals(), context_instance=RequestContext(request))
#forms.py
class ManageUserForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('full_name', 'user',)
`I think I need to call an instance but I have no idea how to do so for the non-request users AND still follow the pattern for the template. The template basically is a list of users where the request user (staff user) will be able to change the data in the list.
Thank you for your help!
You have one form for one user. You need a FormSet if you want to use that form to edit multiple tenants. Editing objects and displaying them are entirely different beasts; dont' confuse them.
formset = modelformset_factory(form=ManageUserForm, queryset=tenants)
Update:
You should have one {{ form.management_form }} and the rest of the {% for form in formset %}{{ form }}{% endfor %} in one <form> tag. All of your forms are the first form in the formset.
You should rewrite your template loop to iterate through formset forms instead of tenant objects. The tenant object can be accessed through {{ form.instance }}
Update 2:
You have an extra form because you probably haven't passed in the extra=0 parameter to the modelformset_factory function. These forms are typically used to add/edit data; thus it has support for adding N blank forms for creating.