Flask - Nested FieldList doesn't submit correctly - python

Here is a form with fieldlist nested in a other fieldlist.
The purpose is to create an Excel like form.
There is one row per city and one column per date (cities and dates are dynamic lists).
I append the dates to the city and then I append the cities to the main form.
The form is loading as expected :
But after submition, it looks like that :
How can I prevend the second appending after submition ?
Here is my py file :
from flask import Flask
from flask import Flask, flash, redirect, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList, FormField, SubmitField
app = Flask(__name__)
app.config['SECRET_KEY'] = 'key'
class DateForm(FlaskForm):
class Meta:
csrf = False
date = StringField('date')
class CityForm(FlaskForm):
class Meta:
csrf = False
city = StringField('city')
dates = FieldList(FormField(DateForm))
class MainForm(FlaskForm):
cities = FieldList(FormField(CityForm))
submit = SubmitField('Save')
#app.route('/', methods=['GET','POST'])
def home():
cities = ['NY', 'LA']
dates = ["2018", "2019"]
form = MainForm()
if form.validate_on_submit():
flash("validated on submit !", "alert alert-danger")
redirect(url_for('home'))
for c in cities:
city = CityForm()
city.city = c
for d in dates:
date = DateForm()
date.date = d
city.dates.append_entry(date)
form.cities.append_entry(city)
return render_template('template.html', form = form )
if __name__ == "__main__":
app.run(host="127.0.0.1", port="5000" ,debug=True)
And here is my html template :
<html>
<head>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-6 offset-sm-3 p-0">
<form method="POST" action="{{ url_for('home')}}">
{{ form.hidden_tag() }}
<div class="form-group">
<table>
{% for c in form.cities %}
<tr>
<td>{{ c.city }}</td>
{% for d in c.dates %}
<td>{{ d.date.data }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
<div class="form-group">
{{ form.submit(class="btn btn-secondary") }}
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm-6 offset-sm-3 p-0">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="{{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
</div>
</body>
</html>

As per comments - missing a return:
return redirect(url_for('home'))

Related

How to paginate in django for filtered datas

views.py
import datetime
from .filters import MyModelFilter
from django.shortcuts import render
import pymysql
from django.http import HttpResponseRedirect
from facligoapp.models import Scrapper
from django.db.models import Q
from django.utils import timezone
import pytz
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
users = ""
def index(request):
if request.method == "POST":
from_date = request.POST.get("from_date")
f_date = datetime.datetime.strptime(from_date,'%Y-%m-%d')
print(f_date)
to_date = request.POST.get("to_date")
t_date = datetime.datetime.strptime(to_date, '%Y-%m-%d')
print(t_date)
get_records_by_date = Scrapper.objects.all().filter(Q(start_time__date=f_date)|Q(end_time__date=t_date))
print(get_records_by_date)
filtered_dates = MyModelFilter(request.GET,queryset=get_records_by_date)
page = request.GET.get('page', 1)
paginator = Paginator(filtered_dates.qs, 5)
global users
try:
users = paginator.get_page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
else:
roles = Scrapper.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(roles, 5)
try:
users = paginator.page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
return render(request, "home.html", {"users": users})
return render(request, "home.html", {"users": users})
filters.py:
import django_filters
from.models import Scrapper
class MyModelFilter(django_filters.FilterSet):
class Meta:
model = Scrapper
# Declare all your model fields by which you will filter
# your queryset here:
fields = ['start_time', 'end_time']
home.html
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<body>
<style>
h2 {text-align: center;}
</style>
<h1>Facilgo Completed Jobs</h1>
<form action="" method="post">
{% csrf_token %}
<label for="from_date">From Date:</label>
<input type="date" id="from_date" name="from_date">
<label for="to_date">To Date:</label>
<input type="date" id="to_date" name="to_date">
<input type="submit"><br>
</form>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Summary Details</h2>
<table id="bootstrapdatatable" class="table table-striped table-bordered" width="100%">
<thead>
<tr>
<th>scrapper_id</th>
<th>scrapper_jobs_log_id</th>
<th>external_job_source_id</th>
<th>start_time</th>
<th>end_time</th>
<th>scrapper_status</th>
<th>processed_records</th>
<th>new_records</th>
<th>skipped_records</th>
<th>error_records</th>
</tr>
</thead>
<tbody>
{% for stud in users %}
{% csrf_token %}
<tr>
<td>{{stud.scrapper_id}}</td>
<td>{{stud.scrapper_jobs_log_id}}</td>
<td>{{stud.external_job_source_id}}</td>
<td>{{stud.start_time}}</td>
<td>{{stud.end_time}}</td>
<td>{{stud.scrapper_status}}</td>
<td>{{stud.processed_records}}</td>
<td>{{stud.new_records}}</td>
<td>{{stud.skipped_records}}</td>
<td>{{stud.error_records}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if users.has_other_pages %}
<ul class="pagination">
{% if users.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% if user.number|add:'-4' > 1 %}
<li>…</li>
{% endif %}
{% for i in users.paginator.page_range %}
{% if users.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% elif i > users.number|add:'-5' and i < users.number|add:'5' %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if users.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
</div>
</div>
</div>
</body>
</html>
I need to get only the datas which I have filtered et_records_by_date = Scrapper.objects.all().filter(Q(start_time__date=f_date)|Q(end_time__date=t_date)) in pagination. But when I click the next page its showing different datas. Is there any solution to get only the datas for the particular query. When I post the datas the of dates the 1st pages is showing the correct details but when I click page 2 its showing the other datas
When you click on 'next page' you're performing a GET request, and not a POST request. Which means it will go into the else block, which has no filtering but just returns all the Scrapper objects.
You're better off including the from_date and to_date in a GET request and not using a POST request.
If you're using a form you can simply set the method:
<form method="GET" action="..." />
Shared method
def paginate(request,obj,total=25):
paginator = Paginator(obj,total)
try:
page = int(request.GET.get('page', 1))
except:
page = 1
try:
obj_list = paginator.page(page)
except(EmptyPage,InvalidPage):
obj_list = paginator.page(paginator.num_pages)
return obj_list
View
users = UserInfo.objects.all()
data = {
'users': paginate(request,users,15),
}
return render(request,self.template_name,data)
HTML
{% include './pagination.html' with obj=users %}
pagination.html file
{% if obj.paginator.num_pages > 1 %}
<div class="text-center">
<ul class="pagination">
<li class="{% if not obj.has_previous %} disabled{% endif %}">
{% if obj.has_previous %}
<a data-page="{{obj.previous_page_number}}" href="?page={{obj.previous_page_number}}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}" aria-label="{% trans 'Previous' %}" tabindex="-1"> « </a>
{% else %}
<a data-page="0" class="" href="javascript:void(0);" tabindex="-1">«</a>
{% endif %}
</li>
{% for i in obj.paginator.page_range %}
{% if obj.number == i %}
<li class="active">
<a data-page="0" class="" href="javascript:void(0)">{{ i }}</a>
</li>
{% elif i > obj.number|add:'-5' and i < obj.number|add:'5' %}
<li class=""><a data-page="{{i}}" class="" href="?page={{i}}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{{ i }}</a>
</li>
{% endif %}
{% endfor %}
<li class="{% if not obj.has_next %} disabled{% endif %}">
{% if obj.has_next %}
<a data-page="{{ obj.next_page_number }}" class="" href="?page={{obj.next_page_number}}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}" tabindex="-1">»</a>
{% else %}
<a data-page="0" class="" href="javascript:void(0);" tabindex="-1">»</a>
{% endif %}
</li>
</ul>
</div>
{% endif %}

Flask: Use multiple instances of one form

My website lets a teacher create multiple questions for an assignment. Once the assignment is created, a student can come and write an answer for each question.
My problem is that only one answer is saved for each entry. For example, there are two questions in an assignment. The answer for question 1 is "asdf", the answer for question 2 is "fdsa". The content for both answers will be saved as "asdf", when they should be unique.
I have tried printing request.form and it looks like this (excluding the csrf_tokens):
('code_content', 'asdf'), ('code_content', 'fdsa'), ('submit', 'Submit Assignment')]
So I know that fdsa is still in there somewhere, but I'm not able to access it. If this is important, there were two csrf_tokens that were the exact same when printing request.form.
To get that data, I created a 'GetQuestionContent()' form for as many questions in the assignment. Like this:
questions = []
question_content_forms = []
for question in Question.query.all():
if int(question.owner) == int(assignment_id):
questions.append(question)
question_content_forms = [GetQuestionContent() for item in range(0, len(questions))]
Then, in the HTML, I write the form like this:
<form method="POST">
{% for question in questions %}
<div class="accordion-item">
<h2 class="accordion-header" id="heading{{ question.id }}">
<button class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse{{ question.id }}"
aria-expanded="false"
aria-controls="collapse{{ question.id }}">
{{ question.title }}
</button>
</h2>
<div id="collapse{{ question.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading{{ question.id }}"
data-bs-parent="#questionsAccordion">
<p>
<small>
<strong>Question Description</strong>
<br>
{% if question.description != "" %}
{{ question.description|safe }}
{% else %}
<em>The assignment creator did not provide a description for this question.</em>
{% endif %}
</small>
</p>
{{ question_content_forms[loop.index - 1].hidden_tag() }}
{% if question.type == "code" %}
<div class="code-box">
{{ question_content_forms[loop.index - 1].code_content(id = "editor") }}
</div>
{% endif %}
{% if question.type == "text" %}
{{ question_content_forms[loop.index - 1].text_content|safe }}
{% endif %}
</div>
</div>
{% endfor %}
{% if current_user.account_type == "Student" %}
{{ submit_button.submit(class="btn btn-outline-success mt-3", value="Submit Assignment")}}
{% endif %}
</form>
When the user presses submit, I want to get every answer and put it into their own entry for the StudentQuestionSubmission table in my database. This is what that code looks like:
if request.method == "POST" and submit_button.data:
print(request.form)
index = 0
for question in questions:
question_to_submit = StudentQuestionSubmission(question_id = int(question.id),
student_id = int(current_user.id))
if question.type == "code":
question_to_submit.question_content = question_content_forms[index].code_content.data
elif question.type == "text":
question_to_submit.question_content = question_content_forms[index].text_content.data
print(f"\n\n{ question_content_forms[index].code_content.data } \n \
{ question_content_forms[index].text_content.data } \n\n")
index += 1
db.session.add(question_to_submit)
assignment_to_submit = StudentAssignmentSubmission(assignment_id = int(assignment_id),
student_id = int(current_user.id),
has_submitted = True,
submission_date = date.today())
db.session.add(assignment_to_submit)
db.session.commit()
flash(f"'{assignment.name}' has been succesfully submitted.")
return redirect(url_for('classroom_assignments_list', class_id = class_id, paper_id = paper_id))
You can see that I print the data of the textboxes. It will output 'asdf' on both iterations even if I wrote something entirely different for question 2.
I appreciate any help. Thank you.
EDIT: 'hackily' getting the content from multiple instances of the same form using request.form.to_dict(flat=False)['your_form_field']
Here's the new code:
if request.method == "POST" and submit_button.data:
code_content = request.form.to_dict(flat=False)['code_content']
text_content = request.form.to_dict(flat=False)['text_content']
code_content_index = 0
text_content_index = 0
for question in questions:
question_to_submit = StudentQuestionSubmission(question_id = int(question.id),
student_id = int(current_user.id))
if question.type == "code":
question_to_submit.question_content = code_content[code_content_index]
code_content_index += 1
elif question.type == "text":
question_to_submit.question_content = text_content[text_content_index]
text_content_index += 1
print(f"\n\n{ question_to_submit.question_content } \n\n")
db.session.add(question_to_submit)
assignment_to_submit = StudentAssignmentSubmission(assignment_id = int(assignment_id),
student_id = int(current_user.id),
has_submitted = True,
submission_date = date.today())
db.session.add(assignment_to_submit)
db.session.commit()
flash(f"'{assignment.name}' has been succesfully submitted.")
return redirect(url_for('classroom_assignments_list', class_id = class_id, paper_id = paper_id))
My suggestion is not to create multiple forms, but to dynamically create one form for all the necessary questions.
In this case it is possible to assign a unique id to each field, which indicates that it belongs to the question.
The following example shows you a possible implementation.
An answer field is added to the form for each question, which has the id of the question in its name. Thus, the respective question can be assigned when rendering and querying the input data.
Flask
from flask import (
Flask,
render_template,
request
)
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import TextAreaField, SubmitField
from wtforms.validators import DataRequired
import random
app = Flask(__name__)
app.secret_key = 'your secret here'
db = SQLAlchemy(app)
class Question(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String)
owner = db.Column(db.Integer)
title = db.Column(db.String)
description = db.Column(db.Text)
with app.app_context():
db.drop_all()
db.create_all()
qs = [Question(
type=random.choice(['code', 'text']),
owner=1,
title=f'Question {i+1}',
description=f'Your description here.'
) for i in range(3)]
db.session.add_all(qs)
db.session.commit()
def form_factory(qs):
class F(FlaskForm):
submit = SubmitField('Submit')
for q in qs:
field = TextAreaField(
q.type.title(),
validators=[
# DataRequired()
],
)
setattr(F, f'q-{q.id}', field)
return F
#app.route('/', methods=['GET', 'POST'])
def index():
questions = Question.query.filter_by(owner=1).all()
form = form_factory(questions)(request.form)
if form.validate_on_submit():
for q in questions:
field = getattr(form, f'q-{q.id}')
print(f'Question-{q.id}\n{field.data}\n')
return render_template('index.html', **locals())
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body>
<div class="container my-3">
<form method="post">
{{ form.hidden_tag() }}
<div class="accordion mb-3" id="accordionExample">
{% for q in questions -%}
<div class="accordion-item">
<h2 class="accordion-header" id="heading-{{loop.index}}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse-{{loop.index}}"
aria-expanded="false"
aria-controls="collapse-{{loop.index}}"
>
{{ q.title }}
</button>
</h2>
<div
id="collapse-{{loop.index}}"
class="accordion-collapse collapse"
aria-labelledby="heading-{{loop.index}}"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<div class="mb-3">
{{ q.description|safe }}
</div>
{% set field = form|attr('q-{}'.format(q.id)) -%}
<div>
{{ field.label(class_='form-label') }}
{{ field(class_='form-control' + ('', ' editor')[q.type=='code']) }}
</div>
</div>
</div>
</div>
{% endfor -%}
</div>
<div class="d-grid gap-2">
{{ form.submit(class_='btn btn-primary') }}
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>

Flask Form Handling in Jinja2 For Loop

I have a blog-type application where the homepage displays posts with some information and with an Add Comment form. The form is intended to write to my Comments() model in models.py for that specific post.
The Problem: is that because I am looping through the posts in home.html, the post.id is not available to the home function in routes.py. So, when the form is validated, the comment is applied to all the posts, not just the one in which the comment is added.
The question: how can I get the relevant post.id in the home function from the Jinja forloop and have the comment be applied to the specific post, not just all the posts on the homepage?? I'm not seeing my logic/syntax error - what am I missing here? Thanks
The resulting error: AttributeError: 'function' object has no attribute 'id'
which of course makes sense because the application has no clue what post we are referencing in the Jinja2 forloop in home.html.
here is the Comments db model in models.py:
class Comments(db.Model):
comment_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, nullable=True, primary_key=False)
post_id = db.Column(db.Integer, nullable=True, primary_key=False)
comment = db.Column(db.String(2000), unique=False, nullable=True)
comment_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
class Meta:
database = db
def fetch_post_comments(self, post_id):
comments = Comments.query.filter(Comments.post_id==post_id)
return comments
def fetch_user(self, user_id):
user = User.query.filter(User.id==user_id).first_or_404()
return user.username
def __repr__(self):
return f"Comments ('{self.user_id}', '{self.post_id}', '{self.comment}', '{self.comment_date}')"
And here is my home function in routes.py:
#app.route("/")
#app.route("/home", methods=['GET', 'POST'])
#login_required
def home():
page = request.args.get('page', 1, type=int)
posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
comment_form = CommentForm()
def add_comment(post_id):
if comment_form.validate_on_submit():
new_comment = Comments(user_id=current_user.id,
post_id=post_id,
comment=comment_form.comment_string.data)
db.session.add(new_comment)
db.session.commit()
flash('HOT TAKE! Posted your comment.', 'success')
# return redirect(url_for('post', post_id=post.id))
def get_upvote_count(post_id):
count = Vote.query.filter(Vote.post_id==post_id).count()
return count
def get_flag_count(post_id):
count = Flag.query.filter(Flag.post_id == post_id).count()
return count
def get_comment_count(post_id):
count = Comments.query.filter(Comments.post_id == post_id).count()
return count
def get_favorite_count(post_id):
count = Favorites.query.filter(Favorites.post_id == post_id).count()
return count
def get_youtube_id_from_url(url):
video_id = url.split('v=')[1]
if '&' in video_id:
video_id = video_id.split('&')[0]
base_url = "https://www.youtube.com/embed/"
return base_url + video_id
def get_spotify_embed_url(url):
track_or_playlist = url.split('https://open.spotify.com/')[1].split('/')[0]
base_url = f"https://open.spotify.com/embed/{track_or_playlist}/"
spotify_id = url.split('https://open.spotify.com/')[1].split('/')[1]
embed_url = base_url + spotify_id
return embed_url
return base_url + video_id
return render_template('home.html',
posts=posts,
get_upvote_count=get_upvote_count,
get_comment_count=get_comment_count,
get_flag_count=get_flag_count,
get_favorite_count=get_favorite_count,
comment_form=comment_form,
add_comment=add_comment,
get_youtube_id_from_url=get_youtube_id_from_url,
get_spotify_embed_url=get_spotify_embed_url)
And here is my home.html
{% extends "layout.html" %}
{% block content %}
{% for post in posts.items %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<div align="left">
<a class="mr-2 text-secondary" href="{{ url_for('user_posts', username=post.author.username) }}">{{ post.author.username }}</a>
</div>
<div align="left">
<small class="text-muted">Posted on: {{ post.date_posted.strftime('%Y-%m-%d') }}</small>
</div>
<div align="right">
<a class="mr-2 text-secondary" href="{{ url_for('flag', post_id=post.id, user_id=current_user.id) }}">Flag Post</a>
</div>
<div align="right">
<a class="mr-2 text-secondary" href="{{ url_for('add_favorite', post_id=post.id, user_id=current_user.id) }}">Favorite Post ({{ get_favorite_count(post.id) }})</a>
</div>
<div align="right">
<a class="mr-2 text-secondary" href="{{ url_for('post', post_id=post.id) }}">Comments ({{ get_comment_count(post.id) }})</a>
</div>
</div>
<h3><a class="article-title" href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h3>
<p class="article-content justify-content-center">{{ post.content }}</p>
<br>
{% for url in post.urls.split('||') %}
{% if 'youtube.com' in url %}
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item"
src="{{ get_youtube_id_from_url(url) }}" allowfullscreen="" frameborder="0">
</iframe>
</div>
Link
{% elif 'soundcloud.com' in url %}
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" scrolling="no" frameborder="no"
src="{{ 'https://w.soundcloud.com/player/?url=' + url }}">
</iframe>
</div>
Link
{% elif 'spotify.com' in url %}
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item"
src="{{ get_spotify_embed_url(url) }}" allowfullscreen allow="encrypted-media">
</iframe>
</div>
Link
{% elif 'vimeo.com' in url %}
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" scrolling="no" frameborder="no"
src="{{ 'https://player.vimeo.com/video/' + url.split('https://vimeo.com/')[1] }}">
</iframe>
</div>
Link
{% elif 'tumblr.com' in url %}
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item"
src="{{ url }}" frameborder="0">
</iframe>
</div>
Link
{% else %}
Link
<br>
{% endif %}
{% endfor %}
<br>
<br>
<p class="text-muted"><strong>Tags:</strong></p>
{% for tag in post.tags.replace(' ', ' ').strip(',').split(' ') %}
<a class="btn btn-light" href="{{url_for('tag_posts', tag=tag)}}">{{tag.strip('#').strip(' ').lower() }}</a>
{% endfor %}
<br>
<form method="POST" action="" enctype="multipart/form-data">
{{ comment_form.hidden_tag() }}
<fieldset class="form-group">
<br>
<br>
<p class="text-muted"><strong>Add a comment:</strong></p>
<div class="form-group">
{% if comment_form.comment_string.errors %}
{{ comment_form.comment_string(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in comment_form.comment_string.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ comment_form.comment_string(class="form-control form-control-lg") }}
<!-- {{ add_comment(post_id=post.id) }}-->
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ comment_form.submit(class="btn btn-secondary") }}
</div>
</form>
<br>
<p class="text-muted mt-4"><strong>Street Cred: </strong>{{ get_upvote_count(post.id) }}</p>
<a class="btn btn-secondary mb-4" href="{{url_for('upvote', user_id=post.author.id, post_id=post.id)}}">Upvote</a>
</div>
</article>
{% endfor %}
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if posts.page == page_num %}
<a class="btn btn-secondary mb-4" href="{{ url_for('home', page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('home', page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endblock content %}
A couple of options:
Add the post.id to the url as a parameter or arg, here's the arg method:
Add the url to the form and set the post.id as the arg:
<form method="POST" action="{{url_for('home', post_id=post.id)}}" enctype="multipart/form-data">
And in the route:
new_comment = Comments(user_id=current_user.id,
post_id=request.args.get('post_id', type=int),
comment=comment_form.comment_string.data)
OR
Add a hidden field to your form, and set the value of that to be the post.id:
Add a hidden field onto your form:
post_id = HiddenField()
You'll need to replace the CSRF render (hidden_tag()) to prevent auto rendering of the post_id field:
{{ comment_form.csrf_token }}
Next, set the value of your hidden field data (credit to this answer for this function):
{% set p = comment_form.post_id.process_data(post.id) %}
{{ comment_form.post_id() }}
and finally, in the route, (remove the add_comment declaration):
def home():
# Omitted ...
if comment_form.validate_on_submit():
new_comment = Comments(user_id=current_user.id,
post_id=comment_form.post_id.data,
comment=comment_form.comment_string.data)
db.session.add(new_comment)
# etc...
Hope this helps, note it's not tested

Django view works with default call, but form submission to same view only calls dispatch, and page is blank

I have a class based view (Django 1.7, Python 3.4) called ColorList:
from django.shortcuts import redirect, render
from django.views.generic import ListView
from colorlikenatorizer.models import Color
MIN_SEARCH_CHARS = 2
MIN_SEARCH_CHARS_FOR_DISPLAY = "two"
class ColorList(ListView):
"""
Displays all colors in a table with only two columns: the name
of the color, and a "like/unlike" button.
"""
model = Color
context_object_name = "colors"
def dispatch(self, request, *args, **kwargs):
print("cl.1")
search_text = ""
if(request.method == "POST"):
search_text = request.POST.get("search_text", "").strip()
print("cl.1.2 search_text=" + search_text + "")
global MIN_SEARCH_CHARS
if(len(search_text) < MIN_SEARCH_CHARS):
print("cl.1.3")
search_text = ""
if(search_text != ""):
self.color_search_results = Color.objects.filter(name__contains=search_text)
else:
self.color_search_results = []
print("cl.2")
return super(ColorList, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
print("cl.3")
return super(ColorList, self).get_queryset()
def get_context_data(self, **kwargs):
print("cl.4")
context = super(ColorList, self).get_context_data(**kwargs)
global MIN_SEARCH_CHARS_FOR_DISPLAY
context["color_search_results"] = self.color_search_results
context["MIN_SEARCH_CHARS_FOR_DISPLAY"] = MIN_SEARCH_CHARS_FOR_DISPLAY
return context
It works fine when you call its main url: http://my.ip.address/colorliker/
But when submitting the search form, although the dispatch function is properly called (the search-text is printed to the console on the print("cl.1.2... line), neither get_queryset nor get_context_data are being called, and the web page is totally blank.
Any ideas why?
Template:
<!DOCTYPE html>
<html lang="en">
<HTML><HEAD>
<TITLE>Color Likenatorizer</TITLE>
<meta name="viewport" content="width=device-width"/>
</HEAD>
<BODY>
<table>
<TD ALIGN="left" VALIGN="top">
<form id="search_colors_form_id" method="post" action="{% url 'color_list' %}">
<input type="text" id="search_text" name="search_text"/>
{% csrf_token %}
<input id="id_pic_submit_button" type="submit" value="Search for color"/><BR>
(Requires {{ MIN_SEARCH_CHARS_FOR_DISPLAY }} or more characters)
</form>
{% if color_search_results.count > 0 %}
<UL>
{% for color in color_search_results %} <!-- No colon after "colors" -->
<LI>{{ color.name }}</LI>
{% endfor %}
</UL>
{% endif %}
</TD><TD ALIGN="left" VALIGN="top">
<H1>Color Likenatorizer</H1>
{% if colors.count == 0 %}
<P><I>There are no colors in the database.</I></P>
{% else %}
<TABLE ALIGN="center" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
<TD><B><U>Title</U></B></TD>
<TD>Favorite?</TD>
{% for color in colors %} <!-- No colon after "colors" -->
</TR><TR>
<TD VALIGN="top">{{ color.name }}</TD>
<TD>
<A HREF="{% url 'toggle_color_like' color.id %}">
{% if color.is_favorited %}Yes{% else %}No{% endif %}
</A></TD>
{% endfor %}
</TR></TABLE>
{% endif %}
</TD>
</table>
</BODY></HTML>
urls.py:
from django.conf.urls import patterns, include, url
from django.conf.urls import patterns
from colorlikenatorizer.views import ColorList
urlpatterns = patterns('',
url(r"^$", ColorList.as_view(), name="color_list"),
url(r"^like_color_(?P<color_id>\d+)/$", "colorlikenatorizer.views.toggle_color_like", name="toggle_color_like"),
)
ListView doesn't have POST handler so you're getting 405 error (Method not allowed).

Next and Before Links for a django paginated query

I'm trying to make a search form for Django.
Its a typical search form and then returns a table of matches. I wish to paginate the tables returned.
The problem lies in the Previous and Next buttons.
The links for the return query goes to /records/search/?query=a (search sample is a)
The page outputs the table and its previous and next links. However the links redirect to /records/search/?page=2 and the page displays a blank table.
Any help on which links I should pass for Prev/Next?
search.html:
{% extends 'blank.html' %}
{% block content %}
<div class="row">
<form id="search-form" method="get" action=".">
{{ form.as_p }}
<input type="submit" value="Search" />
</form>
</div>
<br><br>
//display table code//
{% if is_paginated %}
<div class="pagination">
<span class="step-links">
{% if agent_list.has_previous %}
forrige
{% endif %}
<span class="current">
Page {{ agent_list.number }} of {{ agent_list.paginator.num_pages }}.
</span>
{% if agent_list.has_next %}
Next
{% endif %}
</span>
</div>
{% endif %}
{% endblock %}
and the search view:
def search_page(request):
form = SearchForm()
agents = []
show_results=False
if request.GET.has_key('query'):
show_results=True
query=request.GET['query'].strip()
if query:
form=SearchForm({'query': query})
agents = \
Agent.objects.filter(Q(name__icontains=query))
paginator = Paginator(agents, 10)
page = request.GET.get('page')
try:
agents = paginator.page(page)
except PageNotAnInteger:
agents = paginator.page(1)
except EmptyPage:
agents = paginator.page(paginator.num_pages)
variables = RequestContext(request,
{ 'form': form,
'agent_list': agents,
'show_results': show_results,
'is_paginated': True,
}
)
return render_to_response('search.html', variables)
I've seen the similar questions but I can't understand/make them work. Any help?
Edit:
For a quickfix (haven't really looked at the cons)
I added a variable in my view:
variables = RequestContext(request,
{ 'form': form,
'agent_list': agents,
'show_results': show_results,
'is_paginated': True,
**'query': query,**
}
)
Where query without the quotes is the recieved query variable.
Then simply change the URL to:
Previous
If you have a better way of answering the question, please do or appending a URL to your currently opened URL.
I would recommend putting the solution in a template tag like so:
myapp/templatetags/mytemplatetags.py:
from django import template
register = template.Library()
#register.simple_tag
def url_replace(request, field, value):
d = request.GET.copy()
d[field] = value
return d.urlencode()
#register.simple_tag
def url_delete(request, field):
d = request.GET.copy()
del d[field]
return d.urlencode()
Then from templates do:
{% load mytemplatetags %}
...
previous
you can use {{ request.get_full_path }} this tag to get current url.
Next
this worked for me
The below works before and after a search form has been submitted:
Views.py
class PostListView(ListView):
model = Post #.objects.select_related().all()
template_name = 'erf24/home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts' # default >> erf24/post_list.html
ordering = ['date_posted']
paginate_by = 3
def is_valid_queryparam(param):
return param != '' and param is not None
def invalid_queryparam(param):
return param == '' and param is None
class SearchView(ListView):
model = Post #.objects.select_related().all()
template_name = 'erf24/home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts' # default >> erf24/post_list.html
ordering = ['date_posted']
paginate_by = 3
def get_queryset(self): # new
key = self.request.GET.get('key')
minp = self.request.GET.get('min')
maxp = self.request.GET.get('max')
if is_valid_queryparam(key):
obj = Post.objects.filter(Q(content__icontains=key) | Q(location__icontains=key)).distinct().order_by('date_posted')
if is_valid_queryparam(minp):
obj = Post.objects.filter(Q(price__gte=minp)).distinct().order_by('date_posted')
if is_valid_queryparam(maxp):
obj = Post.objects.filter(Q(price__lte=maxp)).distinct().order_by('date_posted')
if is_valid_queryparam(minp) & is_valid_queryparam(maxp):
obj = Post.objects.filter(Q(price__gte=minp) & Q(price__lte=maxp)).distinct().order_by('date_posted')
if is_valid_queryparam(key) & is_valid_queryparam(minp) & is_valid_queryparam(maxp):
obj = Post.objects.filter(Q(content__icontains=key) | Q(location__icontains=key)).distinct()
obj = obj.filter(Q(price__gte=minp) & Q(price__lte=maxp)).order_by('date_posted')
if invalid_queryparam(key) & invalid_queryparam(minp) & invalid_queryparam(maxp):
obj = Post.objects.all()
return obj
url.py
urlpatterns = [
path('', PostListView.as_view(), name='erf24-home'),
path('search/', SearchView.as_view(), name='erf24-search'),
]
Home.html
<form action="{% url 'erf24-search' %}" method="GET">
<div class="form-group">
<label for="inputAddress">Search keyword</label>
<input type="text" class="form-control" id="key" name="key" placeholder="keyword">
</div>
<label for="">Price</label>
<div class="form-row">
<div class="form-group col-md-6">
<input type="number" class="form-control" id="min" name="min" placeholder="min price">
</div>
<div class="form-group col-md-6">
<input type="number" class="form-control" id="max" name="max" placeholder="max price">
</div>
</div>
<button type="submit" class="btn btn-primary btn-sm mt-1 mb-1">Search</button>
<button type="reset" class="btn btn-secondary btn-sm mt-1 mb-1">Clear</button>
</form>
{% for post in posts %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}" alt="">
{{ post.author }}
<small class="text-muted">{{ post.date_posted }}</small>
<!-- use |date: "specs" to filter date display -->
</div>
<h2>
{{ post.price }}
</h2>
<p class="article-content">{{ post.content }}</p>
<p class="article-content">{{ post.location }}</p>
<p><a class="like-btn" data-href="{{ post.get_api_like_url }}" href="">{{ post.likes.count }}
{% if user in post.likes.all %} Unlike
{% else %} Like
{% endif %}
</a></p>
</div>
{% for image in post.image_set.all %}
<img class="account-img" src="{{ image.image.url }}" alt="">
{% endfor %}
</article>
{% endfor %}
{% if is_paginated %}
{% if page_obj.has_previous %}
First
Previous
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
{{ num }}
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
{{ num }}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
Next
Last
{% endif %}
{% endif %}
Worked like a charm :) Enjoy!
You can use {{ request.get_full_path }} template tag
Next
you can use this, I use it because I use filters in the url itself, so all the url params are used to build the next or previous url
import re
from django import template
register = template.Library()
PAGE_NUMBER_REGEX = re.compile(r'(page=[0-9]*[\&]*)')
#register.simple_tag
def append_page_param(value,pageNumber=None):
'''
remove the param "page" using regex and add the one in the pageNumber if there is one
'''
value = re.sub(PAGE_NUMBER_REGEX,'',value)
if pageNumber:
if not '?' in value:
value += f'?page={pageNumber}'
elif value[-1] != '&':
value += f'&page={pageNumber}'
else:
value += f'page={pageNumber}'
return value
then, in your pagination nav you can call it like this:
{% append_page_param request.get_full_path page_obj.previous_page_number %}

Categories