not able to POST required data in django templates - python-3.x

I want to POST a form from my html to views.py in django, but I am not able do it.
This is my html. This form should post the url of the downloaded image to the views.py function.
{% for x in photo %}
<a class="down" href="{{x.image.url}}" onclick="myfunc()" download="none">get</a>
<form action="/image_info/" method="POST" id='dform'>
{% csrf_token %}
<input name='d_button' type="hidden" value="{{x.image.url}}">
</form>
{% endfor %}
This is my javascript function to submit form whenever the get link is pressed
<script type="text/javascript">
function myfunc()
{
document.getElementById("dform").submit();
console.log('hello');
}
</script>
this is my views.py. This should take the image url from the form and display it in the new page.
def show_image():
image_url = request.POST.get('d_button', None)
return render(request, 'show_image.html', {'image': image_url)
but my problem is that the form is not returning the url of the image that is clicked instead it is returning the url of first link. for example
link1
link2
link3
link4
if I click on link3, it is downloading the image in link3 but POSTING the url of link1.
This is a bit tricky to explain but this is the best I can.
Thanks in advance.

HTML ids are supposed to be unique. You are looping while generating these forms and hence generate a bunch of duplicate ids, so when you write document.getElementById("dform") the first matching element is selected.
One solution would be to use forloop.counter to generate unique ids and use them. We would set these ids as an attribute on the anchor and pass the anchor element to the onclick function:
{% for x in photo %}
<a class="down" href="{{x.image.url}}" onclick="myfunc(this)" data-target="dform-{{ forloop.counter }}" download="none">get</a>
<form action="/image_info/" method="POST" id='dform-{{ forloop.counter }}'>
{% csrf_token %}
<input name='d_button' type="hidden" value="{{x.image.url}}">
</form>
{% endfor %}
Now in your javascript:
<script type="text/javascript">
function myfunc(elem)
{
document.getElementById(elem.getAttribute("data-target")).submit();
console.log('hello');
}
</script>

Related

What would cause the same Django template include block to behave differently twice on the same page?

I'm trying to include a simple pagination template into a template that lists blog posts, but it gets output without getting interpreted, as in, I see the double curly brace enclosed tag as text in the resultant webpage (see image below).
Strangely, when I also paste same block higher in the page, and it gets interpreted fine. The same include, twice on the same page, different behavior!
Between the two, I iterate over the same object that gets passed to the pagination template, so this is probably something that I don't understand about the state of that object? Or Django's rendering process.
{% extends "blog/base.html" %}
{% block title %}My blog site thing{% endblock %}
{% block content %}
<h1>Blog site</h1>
{% include 'pagination.html' with page_object=posts %}
{% for post in posts %}
<h2>{{ post.title }}</h2>
<p class="date">Published {{post.publish}} by {{post.author}}</p>
{{post.body|truncatewords:5|linebreaks}}
{% endfor %}
{% include 'pagination.html' with page_object=posts %}
{% endblock %}
Pagination.html
<div class="pagination">
<span class="step-links">
{% if page_object.has_previous %}
Previous
{% endif %}
<span class="current">
Page {{ page_object.number }} of {{ page_object.paginator.num_pages }}.
</span>
{% if page_object.has_next %}
Next
{% endif %}
</span>
</div>
views.py for this app
from django.shortcuts import render, get_object_or_404
from .models import Post
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
object_list = Post.published.all()
paginator = Paginator(object_list, 3)
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request, 'blog/post/list.html', {'posts':posts})
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post, status='published',publish__year=year,publish__month=month, publish__day=day)
return render(request, 'blog/post/detail.html', {'post':post})
Here's what the resultant page looks like:
Here's the resultant html (I've added an extra line around the offending output to call it out)
<!DOCTYPE html>
<html>
<head>
<title>My blog site thing</title>
<link href="/static/css/blog.css" rel="stylesheet">
</head>
</html>
<body>
<div id="content">
<h1>Blog site</h1>
<div class="pagination">
<span class="step-links">
<span class="current">
Page 1 of 2.
</span>
Next
</span>
</div>
<h2>Another glorious post</h2>
<p class="date">Published March 2, 2022, 9:26 p.m. by admin</p>
<p>Lorem ipsum text Lorem ipsum …</p>
<h2>Classy post</h2>
<p class="date">Published March 2, 2022, 9:25 p.m. by admin</p>
<p>This is the way</p>
<h2>Another post</h2>
<p class="date">Published March 1, 2022, 7:26 a.m. by admin</p>
<p>Xyz</p>
{% include 'pagination.html' with page_object=posts %}
</div>
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog.</p>
</div>
</body>
I've even gone back and copy-pasted the actual code from the book I'm following (Django 3 by example, by Antonio Mele) and it's identical!
To any kind soul who has read this far, what am I missing?
Ok, so I just understood what was going on, and sharing in case it's useful to anyone else.
My IDE was mangling the formatting of the template file on save.
So the first include was a perfect little
{% include 'pagination.html' with page=posts %}
The second one ended up looking like
{% include
'pagination.html' with page=posts %}
When I turned off the auto-formatting and fixed the whitespace, it rendered fine.
So my big learning – probably obvious to many – is that whitespace matters in django template tags!.

Shopify Liquid: Paginating blog post based on tag

I'm going to try my best to explain this, sorry if it seems confusing.
I have a customized theme. I'm trying to take articles from the blog and put them into 7 separate tagged pages. Essentially to create separate blog templates standard one blog template. The pages bring in a section that paginates by 6.
I'm able to bring the blog articles into the section and paginate, but I'm unable to filter out the unnecessary tags. When I use if/unless statements inside the pagination loop, it just didn't render the unnecessary ones but still paginated based on it, so I had 3 articles on the first page, 2 on the second, and so on. Which make sense why it would do that.
I've tried the 'where' filter on my assign tag and the paginate tag, didn't work. When I asked on the Shopify Slack someone mentioned using the Section Render API with the endpoint '/blogs/{blog_id}/tagged/{tag_id}' but the returns all of the HTML from that page and I don't know how I'd parse and paginate over that. The Section Render API documentation is wanting, to say the least. And my experience with APIs is limited
The closest I've come to answer an is this answer: https://stackoverflow.com/a/60000460/12948634
The problem with the liquid solution is that I can apply a "view" to the tagged posts page but I still have to paginate over it. I still have to use the {% paginate blog.articles %} that apparently doesn't filter. Pagination isn't included in this theme and I'm not quite sure how to add that that to my 'blog-content.liquid' file without disturbing the main tagged page.
Any ideas? Code below:
<div id="article-index-card-section" class="g-flex">
{% assign blog = blogs.{blog_id} %}
{% paginate blog.articles by 6 %}
{% for article in blog.articles %}
<div class="article-index-card g-vertical">
<div style="display: flex; flex-direction: column;">
<img class="article-index-card-image" src="{{ article.image.src | img_url: 'master' }}">
<div class="article-index-card-text">
<h4>{{ article.title }}</h4>
<p class="t-body">{{ article.excerpt }}</p>
</div>
</div>
<div class="article-index-card-btn btn">
Learn More
</div>
</div>
{% endfor %}
<div>
{{ paginate | default_pagination: next: '>', previous: '<' }}
</div>
{% endpaginate %}
</div>
I will probably go the JS route in this case.
Here is what will I probably do, I don't know if it will fit your needs.
Create a separate blog.ajax.liquid template
We will create a new blog template and we will keep only the HTML need for the page to make the request faster and strip unused HTML elements.
{% layout none %}
<div id="article-index-card-section" class="g-flex">
{% paginate blog.articles by 6 %}
{% for article in blog.articles %}
<div class="article-index-card g-vertical">
<div style="display: flex; flex-direction: column;">
<img class="article-index-card-image" src="{{ article.image.src | img_url: 'master' }}">
<div class="article-index-card-text">
<h4>{{ article.title }}</h4>
<p class="t-body">{{ article.excerpt }}</p>
</div>
</div>
<div class="article-index-card-btn btn">
Learn More
</div>
</div>
{% endfor %}
<div>
{{ paginate | default_pagination: next: '>', previous: '<' }}
</div>
{% endpaginate %}
</div>
That's why we add the line {% layout none %} at the top to remove all of the HTML output from the default layout and only output the HTML on the template we create.
Make fetch request to that new template
We make a fetch request to the blog but we must specify that it must used the newly created template and not the default one.
fetch('/blogs/{blog_handle}/tagged/{tag}?view=ajax').then(res => res.text()).then(res => {
document.querySelector('.blog-holder').innerHTML = res;
})
For that we add the ?view=ajax to the end of the request, where the ajax part is the name of the template we created after blog.ajax.liquid.
This request needs to be done for each separate blog, so if you have 10 blogs you will do this request 10 times for each one.
Pagination request
The pagination request will be similar to the request of the blog but you will need to add the page=2 argument to the request where the 2 is the page number.
So something like so:
fetch('/blogs/{blog_id}/tagged/{tag_id}?view=ajax&page=2').then(res => res.text()).then(res => {
document.querySelector('.blog-template').innerHTML = res;
})
Blog main page
I will probably create a static section, where you will be able to select which blog goes on this page.
{%- for block in section.blocks -%}
{%- assign _block = block.settings -%}
{%- assign block_blog = _block.blog -%}
<div class="blog-template" data-handle="{{block_blog}}">
</div><!-- /.blog-template -->
{%- endfor -%}
<script>
document.querySelectorAll('.blog-template').forEach(item => {
const handle = item.getAttribute('data-handle');
// make fetch request and the logic for your tags
})
</script>
{% schema %}
{
"name": "Blogs",
"blocks": [
{
"type": "blog",
"name": "Blog",
"settings": [
{
"type": "blog",
"id": "blog",
"label": "Choose a blog"
}
]
}
]
}
{% endschema %}
This will be my personal choice of direction I go, I don't know if it will fit your needs.

How can I read user inputs from a dynamically created form in flask?

I want to create a dynamic input form and read the user inputs back when a button ("Submit") is pressed, what is the best way (a good way) to do this?
I have tried creating a dynamic form with a FieldList but I was unable to get the values from the user.
This is (hopefully all of) the relevant code:
forms.py
class EntryForm(Form):
field = StringField()
class DynamicForm(FlaskForm):
parameter = FieldList(FormField(EntryForm), min_entries=1)
submit = SubmitField('Submit')
routes.py
#app.route("/new", method=['GET', 'POST'])
def new_form():
form = DynamicForm()
if form.validate_on_submit():
values = form.parameter #This does not work as intended.
do_stuff(values)
return redirect(url_for('index'))
parameter = utils.get_parameter()
return render_template('dynamic_form.html', form=form, parameter=parameter)
where utils.get_parameter() returns an unknown list of parameters. Or to be precise the function expects a parameter and returns a list based on this paramater, but I have omitted this here.
dynamic_form.html
{% extends "layout.html" %}
{% macro render_field(item) %}
<div class="form-group">
<label class="form-control-label">{{ item.label }}</label>
<input type="text" name="{{ item.name }}" class="form-control" value="{{ item.value }}"/>
</div>
{% endmacro %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class ="form-group">
<legend class="border-bottom mb-4">Parameter</legend>
{% for item in form.parameter %}
{{ render_field(item) }}}
{% endfor %}
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
I assume it is because the validate_on_submit() is called after the form is constructed (again) and thus it does not contain the form fields, and especially not the user inputs. Is there a way to prevent this "data loss" or is this a completely wrong approach?
Also, please let me know if information is missing.
You don't want to validate the form when the page first loads. Try this first:
if request.method == "POST" and form.validate_on_submit():
values = form.parameter
do_stuff(values)
return redirect(url_for('index'))
Next, notice that you are redirecting them to your view for def index():. Is that what you want?

Flask: How to output string from Python but displayed as hyperlinks in HTML

So, I'm getting the user input from a HTML textbox, and then returning some urls. But currently, when I directly return the URLS (in string), the HTML urls output aren't clickable; they're in just plaintext. Is there a way to convert the returned python strings to HTML urls?
I can change the format of the returned urls on the python side to anything that can be converted to HTML urls that's clickable.
Python code:
#this gets the query and then returns urls
#app.route('/output', methods=['POST'])
def output():
query_list = request.form['query']
outputResults = someFunction(query_list)
return render_template("output.html", output=outputResults)
HTML code:
{% extends 'layout.html' %}
{% block body %}
<div class="jumbotron text-left">
<body>
<style> p {white-space: pre-wrap; }</style>
<h1>Output</h1>
<p> {{output}}
</p>
</body>
</div>
{% endblock %}
Thanks
if you have multiple links in your query use for loop in your template like this:
{% extends 'layout.html' %}
{% block body %}
<div class="jumbotron text-left">
<body>
<style> p {white-space: pre-wrap; }</style>
<h1>Output</h1>
{% for link in output %}
title_your_link
{% endfor %}
</body>
</div>
{% endblock %}

Passing Flask WTForms validations to Bootstrap Alerts via Flask-Bootstrap

new Flask user here...I am building a pretty file upload button for my flask app. The button essentially makes use of WTForms to validate that only csv and txt files can be uploaded. The upload works, but how can I pass validation errors to the screen as a bootstrap alert? For example:
pressing the upload button will generate an alert "no file selected"
pressing upload with a jpeg will generated "wrong file format"
any suggestions will be appreciated!
My forms.py:
class UploadForm(FlaskForm):
validators = [FileRequired(message='There was no file!'),
FileAllowed(['csv', 'txt'], message='Must be a csv file!')]
input_file = FileField('', validators=validators)
submit = SubmitField(label="Upload")
my route.py:
#app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if request.method == 'POST' and form.validate_on_submit():
input_file = request.files['input_file']
# Do stuff
filename = secure_filename(input_file.filename)
# save file to disk to some folder defined in a separate config file....
data = os.path.join(SOME_UPLOAD_FOLDER, filename)
input_file.save(data)
return redirect(url_for('upload'))
else:
return render_template('upload.html', form=form)
and finally the HTML/CSS/JS:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block html_attribs %} lang="en" charset="utf-8"{% endblock %}
{% block metas %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
{% endblock %}
{% block styles %}
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style>
#browsebutton {
background-color: white;
}
#my-file-selector {
display: none;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="jumbotron">
<h3>File Uploader Example</h3>
<div class="row">
<form class="form-inline center-block" action="/upload" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="input-group">
<label id="browsebutton" class="btn btn-default input-group-addon" for="my-file-selector">
{{ form.input_file(id="my-file-selector") }}
Browse...
</label>
<input type="text" class="form-control" readonly>
</div>
{{ form.submit(class_="btn btn-primary") }}
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<! -- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- pretty upload button -->
<script>
$(function() {
// We can attach the `fileselect` event to all file inputs on the page
$(document).on('change', ':file', function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
// We can watch for our custom `fileselect` event like this
$(document).ready( function() {
$(':file').on('fileselect', function(event, numFiles, label) {
var input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if( input.length ) {
input.val(log);
} else {
if( log ) alert(log);
}
});
});
});
</script>
{% endblock %}
If you are using WTForms, I would recommend using macros to render your form fields. It is detailed here, and you can get an example there (you can find some others online to help you customize rather than having to write it from scratch).
Note that in WTForm's documentation, the macro render_field() checks if your field has errors:
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
You can of course decide not to use macros, in that case, you can just use this snippet above directly in your html form with form.input_file.errors instead of field.errors

Resources