I'm following this tutorial to get nested comments in my django web app:
http://www.maxburstein.com/blog/django-threaded-comments/
<!-- comments -->
<h1>Opinion</h1>
<form id="postcomment" method="post" action="">
{{form.as_p}}
<p><input type="submit" value="Submit" /></p>
{% csrf_token %}
</form>
<ul id="commenters">
{% for c in comment_tree %}
<li id="{{c.id}}" class="c" style="margin-left:{{c.depth|add:c.depth}}em;">
<p class="poster">Anonymous - {{c.date_create|naturaltime}}</p>
<p>{{c.content}}</p>
<p>reply</p>
</li>
{% empty %}
<li>There are currently no comments. You can be first</li>
{% endfor %}
</ul>
<!-- end comments -->
The problem is that I see only 'Submit' button, and information "There are currently no comments. You can be first!" - there is no text area where I can put my comment.
I think my problem could be connected with {{form.as_p}}, but I've checked all my code and can't find an answer.
SOLVED
I've found my problem. It was in views.py.
I do not pass the form variable in return
def address_detail(request, id_address):
adres = get_object_or_404(mod_address, id = id_address)
form = mod_address_comment_form(request.POST or None)
data = mod_address.objects.select_related().all().annotate(
longitude=F('mod_address_localisation__longitude'),
latitude=F('mod_address_localisation__latitude')
)
data = list(data.values('id','mod_address_category','mod_address_comment',
'mod_address_localisation',
'longitude',
'latitude',
))
comment_tree = mod_address_comment.objects.all().order_by('path')
data = json.dumps(data)
data = json.loads(data)
return render(request, 'serwis/address_detail.html', {'address_detail_object': adres, 'comment_tree': comment_tree})
When I changed last line to:
return render(request, 'serwis/address_detail.html', {'address_detail_object': adres, 'comment_tree': comment_tree, 'form':form})
I can see text area.
I leave this comment for another users :)
Related
I have a Django form that receives a text (that I copy from Google Classroom: a bunch of student comments). I use these comments to make student's attendance. What I want to achieve is:
Accessing /insertion/ url via GET user receive the page form as a response, to choose the class (class01, class02, etc) and to past the text
When the user clicks on submit in this form (post method), it is redirect to the same /insertion/ url, but now the form is bound to the data submited, and the page shows a preview page (based on a boolean variable I'm passing through context), showing what students are present and what are absent based on the text informed. At that page, a new submit button will be shown below a text like "if everything's ok, hit the ok button".
After click this ok button, a pdf will be generated and the user will be redirected to /files/ url, to see the generated pdf and previous generated pdf.
views.py
def insertion(request):
context = {}
if request.method == 'GET':
form = AttendanceDataForm()
context.update({"form": form})
if request.method == 'POST':
form = AttendanceDataForm(request.POST)
context.update({"form": form})
if form.is_valid():
lesson = form.cleaned_data['lesson']
raw_text = form.cleaned_data['raw_text']
# Get course students
course_students = md.Student.objects.filter(course_id=lesson.course_id)
# Get present students based on raw text informed
present_students = [s for s in course_students if s.full_name in raw_text]
# Get absent students based on raw text informed
absent_students = [s for s in course_students if s.full_name not in raw_text]
context.update({
"present_students": present_students,
"absent_students": absent_students,
"render_preview": True
})
context.update({"active_freq": True})
return render(request, 'core/insertion.html', context)
def files(request):
context = {}
if request.method == 'POST':
# How can I access all expensive calculation I did in the previous view?
context.update({"active_gen": True})
return render(request, "core/files.html", context)
insertion.html
<div class="row">
<div class="col-12 col-md-6">
<h3>Informar Frequência</h3>
{% crispy form %}
</div>
<div class="col-12 col-md-6">
{% if render_preview %}
<div class="container">
<div class="row p-4 bg-white rounded mt-4">
<div class="col-12 col-sm-6">
<h5>Alunos presentes</h5>
<ul class="previewer-list">
{% for student in present_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-6">
<h5>Alunos ausentes</h5>
<ul class="previewer-list">
{% for student in absent_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
</div>
<p class="mt-3">If everything's ok, hit the OK button</p>
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary">OK</button>
</form>
</div>
{% endif %}
</div>
</div>
I could get to implement 1 and 2, but 3 is a mistery right now. What I couldn't get is how I can access the expensive calculations I did in insertion view in the files view. How can I do that?
Here's a solution using session framework.
We'll save the calculations in the session and access those values in another view later.
For starters, we'll just save the ids (pk) of the students instead of the student instances because they are not JSON serializable [See note below].
def insertion(request):
# do expensive calucations ...
present_ids = [s.pk for s in present_students]
absent_ids = [s.pk for s in absent_students]
request.session['attendance_data'] = {
'present_ids': present_ids,
'absent_ids': absent_ids
}
def files(request):
attendance_data = request.session.get('attendance_data')
if not attendance_data:
# show error or something else ...
pass
present_students = md.Student.objects.filter(
pk__in=attendance_data['present_ids']
)
absent_students = md.Student.objects.filter(
pk__in=attendance_data['absent_ids']
)
# generate the pdf ...
Note: If you wish, you can also save the student instances in the session but you'll have to change the SESSION_SERIALIZER setting to use the PickleSerializer. See notes about session serialization.
You could submit the primary keys as form data in hidden fields. Just choose an appropriate delimiter based on your primary key (for example, don't delimit with a hyphen if you use a GUID primary key).
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<input type="hidden"
name="present"
value="{% for s in present_students %}{{ s.pk }},{% endfor %}"
>
<input type="hidden"
name="absent"
value="{% for s in absent_students %}{{ s.pk }},{% endfor %}"
>
<button type="submit" class="btn btn-primary">OK</button>
</form>
Then in the view you can pick up the PKs in the view from the form data then request.
def files(request):
context = {}
if request.method == 'POST':
present_pks = request.POST.pop('present').split(',')[:-1]
absent_pks = request.POST.pop('absent').split(',')[:-1]
# do type conversions if needed
...
# Because we already have the pks separated, we can combine them
# for the query in order to do just 1 query
course_students = md.Student.objects.filter(pk__in=present_pks + absent_pks).all()
absent_students = []
present_students = []
for student in course_students:
if student.pk in absent_pks:
absent_students.append(student)
else:
present_students.append(student)
I'm working on a Flask webapp that asks (internal) users to select options for an arbitrary number of files that will be in a directory on our network so that some other scripts can process the files according to those options (not relevant to this issue, but if you're curious they are A/V files we need to process).
The problem I'm having is that I can't seem to both dynamically generate the form fields needed (there could be 0 - dozens of files in the server directory the app is looking in) and collect the form data for each instance of the form class I created for the input objects. How do you shoehorn n form instances into an instance of another form class??
I have a base IngestForm class and an ObjectForm class that describes fields pertaining to each individual object. My basic suspicion is that wtforms can't have a subclass that includes other forms... but I can print everything out at various steps before the form gets POSTed and see all the data that I expect as a dict, and I can see that the sub-forms are there as wtforms objects. From my index.html template I can see all the expected data from the ObjectForm instances. But once the super-form is posted, all that is returned is a blank choicesDict (see below) and the submit value. Does the IngestForm instance get reinitialized or something weird when I hit Submit?
Here's what I have now. I have set up a dict for choices where each key is the path of the file in question and the value is an instance of the ObjectForm class:
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = {}
# also tried just targetObject = wtforms.FormField(ObjectForm)
submit = wtforms.SubmitField('Submit')
routes.py:
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
# also tried setattr(forms.IngestForm,'choicesDict',choices)
form = forms.IngestForm()
form.choicesDict = choices
if form.is_submitted():
return redirect(url_for('status'))
return render_template(
'index.html',title='Index',objects=objects,form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
# DO THE STUFF
ingest.html template:
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{url_for('status')}}" method='POST'>
<div>
{% for item,vals in form.choicesDict.items() %}
<p>{{vals.targetBase.data}}</p>
<p>{{vals.option1.label}}{{vals.option1()}}</p>
<p>{{vals.option2.label}} {{vals.option3()}}</p>
<p>{{vals.etc.label}}{{vals.etc()}}</p>
{%endfor%}
</div>
{{form.submit()}}
</form>
{% endblock %}
status.html template just takes the POST data. Not really relevant here except to say I can see it is getting none of the choicesDict
OK so I solved this in a really hack-y way but whatever. I used a jinja2 macro following this example and in my form template constructed field names/ids that are unique to the files I'm interested in.
So for each file ['a.mov','b.mov','c.mp4'] in my network directory, I create a dict like so: {'a.mov': SubclassObjectForm, 'b.mov': SubclassObjectForm } and I have a MainForm instance field that includes this dict. When I render the form, the jinja macro creates name and id attributes for the <label> and <input> fields as needed that include a prefix for the file in question.
For example <input name='targetObjectFilePath-movieA.mov' value='/full/path/to/file' type='hidden>.
When the form gets POSTed, it's just a matter of pulling the relevant bits of data in my view.
I hope this helps someone! It might not be elegant or 'pro' but it gets my task done. Next step... styling!
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
targetBase = wtforms.HiddenField('targetObjectBasename')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = wtforms.HiddenField(default='no choices')
submit = wtforms.SubmitField('Submit')
routes.py
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
form = forms.IngestForm()
form.choicesDict = choices
return render_template(
'index.html',title='Index',form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
data = request.form.to_dict(flat=False)
# DO THE STUFF
index.html
{% import "macros.html" as macros %}
{% extends "base.html" %}
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{ url_for('status') }}" method='POST'>
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{# iterate over the form dict with subforms included: #}
{% for item,vals in form.choicesDict.items() %}
<div>
{# iterate over subform fields and include target file basename #}
{% for field in vals %}
{{macros.render_field(field,vals.targetBase.data)}}
{% endfor %}
</div>
{%endfor%}
{{form.submit()}}
</form>
{% endblock %}
macros.html
{% macro render_field(field, uniqueName) %}
<p>
{# I only care about 2 kinds of data: filenames/paths and boolean options. #}
{% if field.type == 'BooleanField' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ field.label.text }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="checkbox" default=""></input>
{# render hidden input for full path for later processing #}
{% elif field.name == 'targetPath' %}
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{# use basename for local id purposes and display value as label for users #}
{% elif field.name == 'targetBase' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ uniqueName }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{% endif %}
</p>
{% endmacro %}
I have a template with lots of forms on it, all wrapped in one form element. I have on MultiForm that is comprised of 4 regular forms, and two formsets. The formsets have been overridden to use custom formset classes.
I render the management forms in the templates, and can see the relevant info in the post.
For the formsets, I initialize the page with only one form visible.
When I try to submit the combined form I get the following error:
ManagementForm data is missing or has been tampered with
I have searched everywhere for the answer, and read about 15 posts on stack overflow with the same error, but none of the solutions seem to help.
The error page highlights the following line:
{{ beneficiaries.management_form }}
Template:
<form class='pension_form' id='implementation_form' action="{% url "confirmation_form" %}" method="post">
{% csrf_token %}
<ul>
{{ the_form.user_info.as_ul }}
</ul>
<ul>
{{ the_form.spouse_info.as_ul }}
</ul>
<div class='formset_container'> {{ children.management_form }}
{% for form in children %}
<div class='formset'><ul>{{ form.as_ul }} </ul><a class="glyphicon glyphicon-plus"></a></div>
{% endfor %}
</div>
<ul>
{{ the_form.employer_info.as_ul }}
</ul>
<ul>
<li>{{ the_form.beneficiary_general.WHO_BENEFITS }}</li>
</ul>
<div id='beneficiary_info_container' style='display:none;'>
<div class='formset_container'>
{{ beneficiaries.management_form }}
{% for form in beneficiaries %}
<div class='formset' >
<ul>{{ form.as_ul }}</ul><a class="glyphicon glyphicon-plus"></a></div>
{% endfor %}
</div>
<ul><li id='inheritance_order'>
{{ the_form.beneficiary_general.BENEFICIARIES_DIE.label_tag }}
{{ the_form.beneficiary_general.BENEFICIARIES_DIE }}
</li>
</ul>
</div>
<button class='btn btn-default main-btn'>{% trans "_Continue" %}
</form>
View:
def show_confirmation_form(request):
ChildFormSet = formset_factory(ChildInfo, formset=ChildInfoFormSet,
extra=14, can_delete=True)
BeneficiaryFormSet = formset_factory(BeneficiaryInfo, formset=BeneficiaryInfoFormSet,
extra=10, can_delete=True)
multi_form_prefix = 'main_form'
child_prefix = 'children_info'
beneficiary_prefix = 'beneficiary_info'
if request.method == 'POST':
form = ConfirmationForm(request.POST, prefix=multi_form_prefix)
children_forms = ChildFormSet(request.POST, prefix=child_prefix)
beneficary_forms = BeneficiaryFormSet(request.POST,
prefix=beneficiary_prefix)
if form.is_valid():
#not ready yet
return HttpResponseRedirect('/thanks/')
else:
form = ConfirmationForm(prefix=multi_form_prefix)
children_forms = ChildFormSet(prefix=child_prefix)
beneficary_forms = BeneficiaryFormSet(prefix=beneficiary_prefix)
context = {'the_form' : form, 'children' : children_forms,
'beneficiaries' : beneficary_forms}
return render(request, "confirmation_form.html", context)
Forms.py
class BeneficiaryInfo(forms.Form):
SHEM_PRATI_MUTAV = forms.CharField(label=_("First_Name"))
SHEM_MISHPACHA_MUTAV = forms.CharField(label=_("Last_Name"))
MISPAR_ZEHUT_MUTAV = forms.IntegerField(label=_("Mispar_Zehut"))
ACHUZ_HALUKA = forms.IntegerField(label=_("Percent_Allocate"))
class BeneficiaryInfoFormSet(BaseFormSet):
def clean(self):
"""
Adds validation to check that no two links have the same anchor or URL
and that all links have both an anchor and URL.
"""
if any(self.errors):
return
teudot_zehut = []
distribution_total = 0
for form in self.forms:
if form.cleaned_data:
teudat_zehut = form.cleaned_data['MISPAR_ZEHUT_MUTAV']
#allow empty forms.
if teudat_zehut:
if teudat_zehut in teudot_zehut:
form.add_error(None, 'No mutavim can share teudot_zehut')
distribution_total += int(form.cleaned_data['ACHUZ_HALUKA'])
if distribution_total != 100:
form.add_error(None, 'Distribution Total must be 100')
In case someone runs into a similar problem:
The problem was the I only showed the formsets if a certain checkbox was checked, and the management form was in the hidden area. I moved it out of the div that was hidden and it worked perfectly.
I'm trying to set up a user profile where you can enter skills. Entering the skills and save them in the databse already works. Now I want to give the user the opportunity to delete every single one of them with a button click. I tried posting the ID of each skill on button click in the URL and read it out in my view to get the item and delete it, but that does not work like I thought and I cant find out why...
MY VIEW
def profile_settings(request, id=None):
# get logged in user object from session
user_id = request.user.id
# get related userprofile
userprofile = UserProfile.objects.get(pk=user_id)
# get all skills of the userprofile
user_skills = Skill.objects.filter(user=userprofile)
if request.method == 'POST':
form = SkillForm(request.POST)
if 'delete-skill' in request.POST:
if id:
print(id)
skill = Skill.objects.get(pk=id).delete()
elif 'add-skill' in request.POST:
if form.is_valid():
# get data from form
name = form.cleaned_data['name']
category = form.cleaned_data['category']
rating = form.cleaned_data['rating']
# create new skill object for a user
new_skill = Skill(name=name, category=category, rating=rating, user=userprofile)
# save it in the database
new_skill.save()
else:
form = SkillForm()
return render(request, 'profile-settings.html', {'skillform': form, 'existing_skills': user_skills})
MY URLS
urlpatterns = [
url(r'^landing', views.landing, name='landing'),
url(r'^neuigkeiten', views.news, name='news'),
url(r'^profileinstellungen/', views.profile_settings, name='profileinstellungen'),
url(r'^profileinstellungen/(?P<id>\d+)/$', views.profile_settings, name='profileinstellungen'),
]
MY TEMPLATE
{% extends 'base.html' %}
{% block content %}
<form method="post" style="margin-top: 300px">
{% csrf_token %}
{{ skillform }}
<input type="submit" value="Hinzufügen" name="add-skill "/>
</form>
<form method="post">
{% csrf_token %}
<ul>
{% for skill in existing_skills %}
<li>{{ skill.name }}</li>
<input href="{% url 'profileinstellungen' id=skill.id%}" type="submit" value="Löschen" name="delete-skill"/>
{% endfor %}
</ul>
</form>
{% endblock %}
It does not post the ID of the skill in the URL. Is there an alternative approach?
Try to change this :
<input href="{% url 'profileinstellungen' id=skill.id%}" type="submit" value="Löschen" name="delete-skill"/>
by this :
<input href="{% url 'profileinstellungen' %}{{skill.id}}" type="submit" value="Löschen" name="delete-skill"/>
because the "url" will know automatically that "skill.id" was related to "id"
You have forgotten to include dollar signs at the end of the regexes in your URL patterns. It should be:
urlpatterns = [
url(r'^landing$', views.landing, name='landing'),
url(r'^neuigkeiten$', views.news, name='news'),
url(r'^profileinstellungen/$', views.profile_settings, name='profileinstellungen'),
url(r'^profileinstellungen/(?P<id>\d+)/$', views.profile_settings, name='profileinstellungen'),
]
In particular, the problem is the regex r'^profileinstellungen/. Without the dollar, it matches URLs like /profileinstellungen/4/, so the id is not passed to the view.
Secondly, it doesn't make sense to give the input an href attribute. Each input should be in a separate form, and you can set the form action to the URL you wish to submit the post request to.
<ul>
{% for skill in existing_skills %}
<form method="post" action="{% url 'profileinstellungen' id=skill.id%}">
{% csrf_token %}
<li>
{{ skill.name }}
<input type="submit" value="Löschen" name="delete-skill"/>
</li>
</form>
{% endfor %}
</ul>
So basically I want to make a simple form I can enter text and the after I hit submit, see the text.
Here is my forms.py:
class Search(forms.Form):
search = forms.CharField()
Here is my views.py:
def search(request):
context = RequestContext(request)
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
ticker = search.save()
ticker.save()
success = True
else:
print search.errors
else:
search = Search()
return render_to_response('ui/search.html', {"search":search}, context)
Here is the html form that you use to type in (I'm using bootstrap for styling purposes):
<form class="navbar-form navbar-right" role="search" action="/search/" method="post" name="tick">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter stock symbol">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And finally, I want the text entered in the form to be displayed on "search.html" which looks like this currently:
{% extends 'ui/base.html' %}
{% block title %} search {% endblock %}
{% block body_block %}
<br>
<p>test</p>
{{ form.search.data }} <!--I'm pretty sure this is not correct -->
{% endblock %}
Anyone know how I can do this? Thanks.
Your form name is search.
To render the value with modern django, you need to call the value method of the field, therefore your template should look like the following:
{{ search.search.value }}
Your template is wrong, as you suspect.
It is looking for a context variable named "form", but you have given it a context dictionary with a key named "search".
Also, "data" is the argument that you use to build up your Search object (correctly), but when you want to extract the user's input from it, you should use the field names instead, and you need to call value() on them in order to get the bound value. So, to get the contents of the text field called search, you should use search.search.value.
Try changing the line
{{ form.search.data }}
to
{{ search.search.value }}