I've been trying to add a like button that changes to unlike when the User clicks on it, however the button isn't changing because I am unable to pass the context to the home.html file. I know the logic i used will throw an error as the get_object_or_404() will not receive an id from the server however if I pass an argument id to the home() function it will also throw an error as the server doesn't provide an id to the page. I'm sorry for asking a question related to logic and not concepts but I haven't been able to understand which concept could i use here
views.py
from django.shortcuts import render,get_object_or_404
from django.views.generic import ListView
from .models import Blog
from django.http import HttpResponseRedirect
def home(request):
post=get_object_or_404(Blog,id=id)
context={
'posts':Blog.objects.all(),
'is_liked':post.likes.filter(id=request.user.id).exists()
}
return render(request,'blog-home',context)
def like_post(request, blog_id):
post = get_object_or_404(Blog, id=blog_id)
is_liked=False
if post.likes.filter(id=request.user.id).exists():
post.likes.remove(request.user)
is_liked=False
else:
post.likes.add(request.user)
is_liked=True
context={
'is_liked':is_liked
}
return HttpResponseRedirect(Blog.get_absolute_url(blog_id))
def post_detail(request, id):
post=get_object_or_404(Blog, id=id)
context={
'post':post,
'is_liked': post.likes.filter(id=request.user.id).exists(),
}
return render(request, 'blog/post_detail.html',context)
def check_liked(request):
post = get_object_or_404(Blog, id=blog_id)
is_liked=False
if post.likes.filter(id=request.User.id).exists():
is_liked=True
else:
is_liked=False
context={
'is_liked':is_liked
}
return render(request, 'blog/post_detail.html',context)
models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Blog(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
date_posted=models.DateTimeField(default=timezone.now)
author=models.ForeignKey(User, on_delete=models.CASCADE)
likes=models.ManyToManyField(User,related_name='likes',blank=True)
def __str__(self):
return self.title
def get_absolute_url(blog_id):
return reverse('post-detail',args=[str(blog_id)])
urls.py
from django.urls import path
from . import views
urlpatterns=[
path('',views.home,name='blog-home'),
path('<int:blog_id>/like/', views.like_post, name='like_post'),
path('post/<int:id>/', views.post_detail, name='post-detail'),
]
home.html
{% block content %}
{% for post in blogs %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<h2>{{ post.author }}</h2>
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
</div>
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
<div>
<form action="{% url 'like_post' post.id %}">
{% csrf_token %}
<form action="{% url 'is_liked' %}">
{% if is_liked %}
<button type='submit' name='blog_id' value="{{ post.id }}" class="btn btn-danger">Unlike</button>
{% else %}
<button type='submit' name='blog_id' value="{{ post.id }}" class="btn btn-primary">Like</button>
{% endif %}
</form>
</form>
</div>
</article>
{% endfor %}
{% endblock %}
post_detail.html
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<h2>{{ post.author }}</h2>
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
</div>
<h2>{{ post.title }}</h2>
<p class="article-content">{{ post.content }}</p>
</div>
<div>
<form action="{% url 'like_post' post.id %}">
{% csrf_token %}
{% if is_liked %}
<button type='submit' name='blog_id' value="{{ post.id }}" class="btn btn-danger">Unlike</button>
{% else %}
<button type='submit' name='blog_id' value="{{ post.id }}" class="btn btn-primary">Like</button>
{% endif %}
</form>
</div>
</article>
You can use ajax for getting the functionality you want. In your home.html, you can make changes as:
{% block content %}
{% for post in blogs %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<h2>{{ post.author }}</h2>
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
</div>
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
<p class="article-content">{{ post.content }}</p>
</div>
<div>
<button type='button' onclick="change_status(this)" id="{{post.id}}" name='blog_id' value="{{ post.id }}" class="btn btn-primary"></button>
</div>
</article>
{% endfor %}
{% endblock %}
<script>
function change_status($this){
var request_data = $this.id;
console.log("data: " + request_data);
$.post({
url: "url that leads to your view",//url to like_post in your case(pass the request_data value in the url as the url needs the post id)
data : { request_data: request_data},
success : function(json) {
if(data.is_liked == True){ //perform a check of returned data(dont know actual implementation but you can google it)
document.getElementByName('blog_id').innerHTML = "Unlike" }
else{
document.getElementByName('blog_id').innerHTML = "Like"
}
}
})}
</script>
The functionality can be achieved by something like this but you may need to perform some changes in it.
Related
I am working on a Django project that serves as a grocery store. I am trying to set it up so that when people click on checkboxes and press the confirm purchase button, then the values from the checkboxes will print to a new HTML template. The problem I am having is that when I go to the new template it doesn't print the values from the checkboxes.
Views.py
class PostListView(ListView):
model = Post
template_name = 'blog/home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
def inventory(request):
products = request.POST.getlist('products')
for product in products:
a = Post.objects.get(title=product)
a.quantity = a.quantity -1
a.save()
print(products)
return render(request, 'blog/confirm.html')
Home.html
{% extends "blog/base.html" %}
{% block content %}
<form action="{% url 'inventory' %}" method="POST" id="menuForm">
{% for post in posts %}
{% if post.quantity > 0 %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2">{{ post.category }}</a>
</div>
<h2><a class="article-title" >{{ post.title }}</a></h2>
<p class="article-content"> Price: ${{ post.Price }}</p>
<p class="article-content"> Sale: ${{ post.Sale }}</p>
<input type="checkbox" id="product_{{ post.id }}" value="{{ post.title }}" form="menuForm" name="products" > Inventory count: {{ post.quantity }}
</input>
</div>
</article>
{% else %}
{% endif %}
{% endfor %}
<button type="submit" form="menuForm">Confirm Purchase</button>
</form>
{% endblock content %}
confirm.html
{% extends "blog/base.html" %}
{% block content %}
{% for post in posts %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2">{{ post.category }}</a>
</div>
<h2><a class="article-title" >{{ post.title }}</a></h2>
<p class="article-content"> Price: ${{ post.Price }}</p>
<p class="article-content"> Sale: ${{ post.Sale }}</p>
<input type="checkbox" id="product_{{ post.id }}" value="{{ post.title }}" form="menuForm" name="products" > Inventory count: {{ post.quantity }}
</input>
</div>
</article>
{% endfor %}
{% endblock %}
urls.py
path('list/', PostListView.as_view(), name='blog-home'),
path('confirm', views.inventory, name='inventory'),
It's happening because of this you did not passed posts on confirm.html page
def inventory(request):
posts = Post.objects.get(title=product)
products = request.POST.getlist('products')
for product in products:
a = Post.objects.get(title=product)
a.quantity = a.quantity -1
a.save()
print(products)
return render(request, 'blog/confirm.html',{'posts':posts})
I create a post_searchview :
def post_search(request):
form = SearchForm()
query = None
results = []
if 'query' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
query = form.cleaned_data['query']
search_vector = SearchVector('title', 'body')
search_query = SearchQuery(query)
results = Post.published.annotate(
search=search_vector,
rank=SearchRank(search_vector, search_query),
).filter(search=search_query).order_by('-rank')
context = {'form': form, 'query': query, 'results': results}
return render(request, 'blog/post/search.html', context)
And i want to link that to my base.html where i have located an html code for the searchbar (look in the bottom of the file):
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
<a class="navbar-brand" href="{% url 'blog:post_list' %}">
My Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarCollapse" aria-controls="navbarCollapse"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav ml-auto">
<li class="nav-item mr-2">
<input class="form-control" type="text" placeholder="Search" aria-label="Search">
</li>
Also, now i have a search.html file to make the searchs, idk if i should delete it :
{% extends "blog/base.html" %}
{% block title %}Search{% endblock %}
{% block content %}
{% if query %}
<h1>Posts containing "{{ query }}"</h1>
<h3>
{% with results.count as total_results %}
Found {{ total_results }} result{{ total_results|pluralize }}
{% endwith %}
</h3>
{% for post in results %}
<h4>{{ post.title }}</h4>
{{ post.body|safe|truncatewords_html:5 }}
{% empty %}
<p>There are no results for your query.</p>
{% endfor %}
<p>Search again</p>
{% else %}
<h1>Search for posts</h1>
<form method="get">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Search">
</form>
{% endif %}
{% endblock %}
Thanks for reading.
Wrap the <input> in a form tag with the correct action, and make sure the form field has a correct name ("query" in your case), á la
<form method="get" action="{% url "blog:search" %}">
<input name="query" class="form-control" type="text" placeholder="Search" aria-label="Search">
</form>
You do need that template to show the results, don't get rid of it.
I am currently struggling with adding image to my posts in my flaskblog
this is my routes.py imports and new_post function and route
import os
import secrets
from PIL import Image
from flask import render_template, url_for, flash, redirect, request, abort
from flaskblog import app, db, bcrypt
from flaskblog.forms import RegistrationForm, LoginForm, UpdateAccountForm, PostForm
from flaskblog.models import User, Post
from flask_login import login_user, current_user, logout_user, login_required
#app.route("/post/new", methods=['GET', 'POST'])
#login_required
def new_post():
form = PostForm()
if form.validate_on_submit():
if form.post_picture.data:
image_post = form.post_picture.data
post = Post(title=form.title.data, content=form.content.data, author=current_user, post_image=image_post)
db.session.add(post)
db.session.commit()
flash('Your post has been created!', 'success')
return redirect(url_for('home'))
return render_template('create_post.html', title='New Post', post_image=post_image,
form=form, legend='New Post')
This is my forms.py imports and PostForm class
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from flask_login import current_user
from wtforms import StringField, PasswordField, SubmitField, BooleanField, TextAreaField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flaskblog.models import User
class PostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
content = TextAreaField('Content', validators=[DataRequired()])
post_picture = FileField('Add image to your post', validators=[FileAllowed(['jpg', 'png'])])
submit = SubmitField('Post')
This is my models.py file and Post class
from datetime import datetime
from flaskblog import db, login_manager
from flask_login import UserMixin
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_image = db.Column(db.String(20), nullable=True)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
This is my post.html file
{% extends "layout.html" %}
{% block content %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">
{% if post.image %}
<img class="rounded-circle account-img" src="{{ post_image }}">
{% endif %}
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="#">{{ post.author.username }}</a>
<small class="text-muted">{{ post.date_posted.strftime('on %d-%m-%Y at %H:%M:%S %p') }}</small>
{% if post.author == current_user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{{ url_for('update_post', post_id=post.id) }}">Update</a>
<button type="button" class="btn btn-danger btn-sm m-1" data-toggle="modal" data-target="#deleteModal">Delete</button>
</div>
{% endif %}
</div>
<h2 class="article-title">{{ post.title }}</h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
<!-- Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Post?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Do you want to delete this post?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<form action="{{ url_for('delete_post', post_id=post.id) }}" method="POST">
<input class="btn btn-danger " type="submit" value="Delete">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
This is my create_post.html file
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">{{ legend }}</legend>
<div class="form-group">
{{ form.title.label(class="form-control-label") }}
{% if form.title.errors %}
{{ form.title(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.title.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.title(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.content.label(class="form-control-label") }}
{% if form.content.errors %}
{{ form.content(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.content.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.content(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.post_picture.label() }}
{{ form.post_picture(class="form-control-file") }}
{% if form.post_picture.errors %}
{% for error in form.post_picture.errors %}
<span class="text-danger">{{ error }}</span></br>
{% endfor %}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
My problem is the following error when I click on here:
Also, if I change post_image=post_image as post_image=image_post, I receive the following error
in your new_post method you need to pass
return render_template('create_post.html', title='New Post', post_image=image_post,
form=form, legend='New Post')
You are making your life a bit difficult by naming different variables in a very similar way: image_post, post_image, post_picture
This makes it very likely that you get them confused in your code.
Your image_post variable is assigned within the if statement that return redirect(url_for('home')).
So it is not being assigned if you
return render_template('create_post.html', title='New Post', post_image=post_image,
form=form, legend='New Post')
Remember, a method only returns one thing at a time.
I know this is one year later, but I went through this error and I figure it out
you need to edit this:
<div class="content-section">
<form method="POST" action="">
to this:
<div class="content-section">
<form method="POST" action="" enctype="multipart/form-data">
that's it
I'm trying to figure out a way to delete pictures I've uploaded to a carousel in django. Does anyone have a solution to this? I can't find any online examples that are clear to me. You can assume I have imported everything, and that all models, etc. are correct.
My code is below:
carousel.html:
{% load staticfiles %}
{% load filename %}
<div class="container">
<div class="row">
<div class="col-md-12">
<div id="myCarousel" class="carousel slide" data-ride="carousel">
<div class="carousel-inner" role="listbox">
{% for document in documents %}
<div class="item {% if forloop.first %} active {% endif %}">
<div class="row">
<div class="col">
<li>{{document.docfile.name}}</li>
<img src = "{{STATIC_URL}}img/{{document|filename}}" >
<p align="center"><form style="text-align:center" action="{% url 'webportal:delete' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.Document.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.Document.errors }}
{{ form.Document }}
</p>
<p><input type="submit" value="Delete" /></p>
</form></p>
</div>
</div>
</div>
{% endfor %}
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right"></span>
<span class="sr-only">Next</span>
</a>
</div>
<!-- /.carousel -->
</div>
</div>
<form action="{% url 'webportal:carousel' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload" /></p>
</form>
</div>
Views.py
def delete(request, my_id):
Deleted=get_object_or_404(Document, docfile=my_id)
if request.method=='POST':
form=DeleteForm(request.POST, instance=Deleted)
if form.is_valid():
Deleted.delete()
return HttpResponseRedirect('http://127.0.0.1:8000/alzheimers/')
else:
form=DeleteForm(instance=Deleted)
return render_to_response(
'webportal/index.html',
{'documents': documents, 'form': form,},
context_instance=RequestContext(request)
)
# Redirect to the document list after POST
def carousel(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect('http://127.0.0.1:8000/alzheimers/')
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
#documents=DocumentForm().
# Render list page with the documents and the form
return render_to_response(
'webportal/index.html',
{'documents': documents, 'form': form,},
context_instance=RequestContext(request)
)
You don't wanna handle too much in one view. You could, but it makes ugly hard to maintain code. Better add a separate view for deleting. With this in your template loop.
<a href='{% url 'delete_document' pk=document.pk %}'>delete</a>
Then add a new url pattern with a pk argument:
url(r'^document/delete/(?P<pk>\d+)/$', "delete_document", name="delete_document"),
And a view like:
def delete_document(request, pk):
try:
Docuent.objects.get(pk=pk).delete()
except Document.DoesNotExist:
pass
return HttpResponseRedirect(...)
I am using wtforms-recaptcha in order to get the Recaptcha to show.
pip install wtforms-recaptcha
I guided myself on this site to make the installation:
https://pypi.python.org/pypi/wtforms-recaptcha
The problem is that the recaptcha code is being echoed into the form. That is, I see the recaptcha's code on the form and not the recaptcha itself:
<script type="text/javascript" src="https://www.google.com/recaptcha/api/challenge?k=6LeCJvUSAAAAAAvqwJEueVdV0wyNLPtX6KWSTdXp"> </script> <noscript> <iframe src="https://www.google.com/recaptcha/api/noscript?k=6LeCJvUSAAAAAAvqwJEueVdV0wyNLPtX6KWSTdXp" height="300" width="500" frameborder="0"></iframe><br> <textarea name="recaptcha_challenge_field" rows="3" cols="40"> </textarea> <input type="hidden" name="recaptcha_response_field" value="manual_challenge"> </noscript>
Form code on form.py:
from wtforms import PasswordField, StringField, validators, widgets
from wtforms.form import Form
from wtfrecaptcha.fields import RecaptchaField
class ContactForm(Form):
"""Enables the user to provide feedback."""
first_name = StringField('First Name', [
validators.DataRequired()
])
last_name = StringField('Last Name', [
validators.DataRequired()
])
captcha = RecaptchaField('Captcha', [], public_key='6LeCJvUSAAAAAAvqwJEueVdV0wyNLPtX6KWSTdXp', private_key='6LeCJvUSAAAAADcUvYyLv8kt9ARiTAluDGqHBumY', secure=True)
Calling the form from within the HTML:
<form method="post">
{% for field in form %}
<div class="form-group{% if field.errors %} has-error has-feedback{% endif %}">
<div class="row">
<div class="col-xs-12 col-md-4">
{{ field.label(class="control-label") }}
</div>
<div class="col-xs-12 col-md-8">
{{ field(class="form-control") }}
</div>
</div>
{% if field.errors %}
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
{% endif %}
{% for error in field.errors %}
<p class="help-block text-danger">
<span class="glyphicon glyphicon-remove"></span>
{{ error }}
</p>
{% endfor %}
</div>
{% endfor %}
<br>
<button type="submit" class="btn btn-primary">{{ title }}</button>
</form>
Route call code:
#app.route('/contact', methods=['GET', 'POST'])
def contact():
"""Display the contact page."""
form = ContactForm(request.form, captcha={'ip_address': request.remote_addr})
if request.method == 'POST' and form.validate():
return "Thank you for contacting us."
return render_template(
...
)
The issue is that WTForms-RECAPTCH does not return a safe string, but instead returns a unicode string. The underlying issue needs to be fixed here (by returning an instance of wtforms.widgets.core.HTMLString or something else that provides an __html__ method).
To work around the problem for now you should simply mark the field as safe in your template:
<div class="col-xs-12 col-md-8">
{{ field(class="form-control") | safe }}
</div>
Or, alternately, only mark the re-captcha field as safe:
<div class="col-xs-12 col-md-8">
{% if field.short_name == "captcha" %}
{{ field(class="form-control") | safe }}
{% else %}
{{ field(class="form-control") }}
{% endif %}
</div>
There is a PR for this issue and this is fixed as of version 0.3.2