Create a preview screen in Django - python

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)

Related

How to dynamically add input fields in form for FieldList [Flask]?

I'm designing a website with a form that allows the user to input stuff and add more input fields, it sort of works like those inputfields that you enter your skills with, soi you can add more skills if you have more. But I'm struggling to display more than one input fields with FieldList as it only shows the number of min_entries I set it, even when I copy and paste the html code it only accepts the first one.
forms.py (please focus on the contents for now)
class ExerciseForm(FlaskForm):
exercise = StringField(validators=[DataRequired()])
testcase = StringField(validators=[DataRequired()])
class AddTopicForm(FlaskForm):
topicname = StringField('Topic Name', validators=[DataRequired(), Length(min=2, max=100)])
contents = FieldList(StringField(validators=[DataRequired()]), min_entries=1, max_entries=20)
exercises = FieldList(FormField(ExerciseForm), min_entries=0, max_entries=10)
submit = SubmitField('Add Topic')
html (I have no idea how to add one, and how to make it work)
<div class="addtopicform">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset>
<div class="form-group">
{{ form.topicname }}
</div>
<div class="form-group">
{{ form.contents }}
</div>
<button type="button" onclick="addsection()">Add Section</button>
<script>
</script>
<div class="form-group">
{% for field in form.exercises %}
{{ field }}
{% endfor %}
</div>
<div class="form-group form-btn">
{{ form.submit(class="btn") }}
</div>
</fieldset>
</form>
</div>
website.py (The best result is one data in contents, but obviously I want more)
#app.route("/addtopic/", methods=['GET', 'POST'])
def addtopic():
if 'AccountID' in session:
form = AddTopicForm()
if session["Type"] == "teacher":
if request.method == 'POST' and form.validate_on_submit():
print("success")
topicname = request.form['topicname']
contents = form.contents.data
exercises = form.exercises.data
print(topicname)
print(contents)
print(exercises)
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
return render_template('addtopic.html', user=session['Name'], acctype=session["Type"], form=form)
else:
flash("Only teachers can access to this page", "failure")
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
flash('An account is needed to access courses', 'failure')
return redirect(url_for('login'))
PLease help me, I couldnt find any useful resources online as all of them assumed I already have a list or is using the input element instead of flask element, which doesnt work at all with flask forms

Django nested threaded comments - no text area

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 :)

Can I build my form without using Django form?

I'm using Django and I just did a big form Using HTML5 and bootstrap. Can I still send the form via the post method to django if I'm not using it to generate the form? Should I definitely redo my form using Django?
NOTE: There may be a better way of doing this, if there is I'd really like to know, this is just how I have done it in the past.
You will still need a forms.py file in your app.
In forms.py:
from django import forms
class MyForm(forms.Form):
# FORM FIELDS HERE
Then put the form in the context dictionary for your view:
def myView(request):
if request.method == "POST":
# FORM PROCESSING HERE
else:
myform = MyForm() #create empty form
return render(request, "template.html", {"myform": myForm}
Now in your template you can add:
<form id="myForm" name="myFormName" method="post" action=".">
{% csrf_token %}
{% for field in myform %}
{{ field.as_hidden }}
{% endfor %}
</form>
This will add your django form to the page without displaying it. All of your form inputs are given the id id_fieldName where fieldName is the field name you defined in the forms.py file.
Now when the user clicks your "submit" button (which I am assuming is a bootstrap button given the rest of your form is). You can use Jquery to input the bootstrap field values into those of the hidden form.
Something like:
$("#mySubmitButton").click(function() {
$("#id_djangoFormField").val($("#myBootstrapFormField").val());
$("#myForm").submit();
}
);
This will submit the django form with the inputs from bootstrap. This can be processed in the view as normal using cleaned_data["fieldName"].
A bit late I post the solution I found for including a form in a modal in a class based detail view. Dunno if it's really orthodox but it works.
I don't use any Form Class or Model. (Django 3.9)
Within the template, I send a field value of my object in a hidden div. If this value is missing for a special action (because for the most of actions on the object, it's not required), a modal pops asking for updating the given field. This modal is triggered with JS that check the presence (or not) of the required value.
In the modal, I display a list of radio choices buttons in an ordinary form inviting the user to update the field. The form's action leads to a view that will update the given field.
modal.html
<form action="{% url 'update-sku-column' object.pk %}" method="post">
{% csrf_token %}
{% if csv_headers %}
<div class="m-3 ps-3">
{% for header in csv_headers %}
{% for csv_sample in csv_samples %}
{% if forloop.counter0 == forloop.parentloop.counter0 %}
<div class="form-check">
<input class="form-check-input" type="radio" name="chosen-field" value="{{ forloop.counter0 }}">
<label class="form-check-label" for="{{ forloop.counter0 }}">
<span class="ms-3">{{ header }} </span>: <span class="ms-1 text-secondary">{{ csv_sample }}</span>
</label>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
<div class="modal-footer">
<button type="submit" class="btn btn-success">Enregistrer</button>
</div>
</form>
urls.py
[...]
path('flow/<int:pk>/update-sku-column',
set_sku_column, name='update-sku-column'),
[...]
views.py
#login_required
def set_sku_column(request, pk):
if request.method == 'POST':
column = request.POST['chosen-field']
flow = Flow.objects.get(pk=pk)
flow.fl_ref_index = column
flow.save()
return redirect('mappings-list', pk=pk)
[...]
Even if I can imagine it's not the best way, it works.
don't forget the {% csrf_token %}otherwise it won't

Django management form missing or tampered with - management form is in the template

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.

Django - How do I delete single database entries on button click?

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>

Categories