After I posted my comment and decided to refresh the page it adds the same comment over and over on every refresh how can I stop this?
The code for the comment review is at the far bottom in the template.html.
And the review code is under "def product_detail" in the views.py.
Can someone give me some tips so if I refresh it dosen't do another post?
template file:
{% extends "base.html" %}
{% load static %}
{% block page_header %}
<div class="container header-container">
<div class="row">
<div class="col"></div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="overlay"></div>
<div class="container-fluid">
<div class="row">
<div class="col-12 col-md-6 col-lg-4 offset-lg-2">
<div class="image-container my-5">
{% if product.image %}
<a href="{{ product.image.url }}" target="_blank">
<img class="card-img-top img-fluid" src="{{ product.image.url }}" alt="{{ product.name }}">
</a>
{% else %}
<a href="">
<img class="card-img-top img-fluid" src="{{ MEDIA_URL }}noimage.png" alt="{{ product.name }}">
</a>
{% endif %}
</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="product-details-container mb-5 mt-md-5">
<p class="mb-0">{{ product.name }}</p>
<p class="lead mb-0 text-left font-weight-bold">${{ product.price }}</p>
{% if product.category %}
<p class="small mt-1 mb-0">
<a class="text-muted" href="{% url 'products' %}?category={{ product.category.name }}">
<i class="fas fa-tag mr-1"></i>{{ product.category.friendly_name }}
</a>
</p>
{% endif %}
{% if product.get_rating > 0 %}
<small class="text-muted"><i class="fas fa-star mr-1"></i>{{ product.get_rating }} / 5</small>
{% else %}
<small class="text-muted">No Rating</small>
{% endif %}
{% if request.user.is_superuser %}
<small class="ml-3">
Edit |
<a class="text-danger" href="{% url 'delete_product' product.id %}">Delete</a>
</small>
{% endif %}
<p class="mt-3">{{ product.description }}</p>
<form class="form" action="{% url 'add_to_bag' product.id %}" method="POST">
{% csrf_token %}
<div class="form-row">
{% with product.has_sizes as s %}
{% if s %}
<div class="col-12">
<p><strong>Size:</strong></p>
<select class="form-control rounded-0 w-50" name="product_size" id='id_product_size'>
<option value="xs">XS</option>
<option value="s">S</option>
<option value="m" selected>M</option>
<option value="l">L</option>
<option value="xl">XL</option>
</select>
</div>
{% endif %}
<div class="col-12">
<p class="mt-3"><strong>Quantity:</strong></p>
<div class="form-group w-50">
<div class="input-group">
<div class="input-group-prepend">
<button class="decrement-qty btn btn-black rounded-0"
data-item_id="{{ product.id }}" id="decrement-qty_{{ product.id }}">
<span class="icon">
<i class="fas fa-minus"></i>
</span>
</button>
</div>
<input class="form-control qty_input" type="number"
name="quantity" value="1" min="1" max="99"
data-item_id="{{ product.id }}"
id="id_qty_{{ product.id }}">
<div class="input-group-append">
<button class="increment-qty btn btn-black rounded-0"
data-item_id="{{ product.id }}" id="increment-qty_{{ product.id }}">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
</button>
</div>
</div>
</div>
</div>
<div class="col{% if s %}-12 mt-2{% endif %}">
<a href="{% url 'products' %}" class="btn btn-outline-black rounded-0 mt-5">
<span class="icon">
<i class="fas fa-chevron-left"></i>
</span>
<span class="text-uppercase">Keep Shopping</span>
</a>
<input type="submit" class="btn btn-black rounded-0 text-uppercase mt-5" value="Add to Bag">
</div>
<input type="hidden" name="redirect_url" value="{{ request.path }}">
{% endwith %}
</div>
</form>
</div>
</div>
</div>
<div class="w-full mt-6">
<h2 class="text-xl">Reviews</h2>
{% if request.user.is_authenticated %}
<form method="post" action="." class="mt-6 mb-6 p-6 bg-gray-100 rounded-xl">
{% csrf_token %}
<div>
<label>Rating</label><br>
<select class="form-control" name="rating">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<div class="form-group">
<label for="comment">Comment</label>
<textarea class="form-control" rows="5" id="comment" name="content"></textarea>
</div>
<button type="submit" class="btn btn-primary mb-2">Submit</button>
</form>
{% endif %}
{% for review in reviews %}
<div class="px-4 py-6 bg-gray-100 rounded-xl mb-4">
<strong>Author:</strong> {{ review.created_by }}<br>
<strong>Rating:</strong> {{ review.rating }}/5<br>
<strong>Date:</strong> {{ review.created_at|date:"Y-m-d" }}<br>
<strong>Comment:</strong><br>
{{ review.content }}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block postloadjs %}
{{ block.super }}
{% include 'products/includes/quantity_input_script.html' %}
{% endblock %}
views.py:
from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.db.models.functions import Lower
from .models import Product, Category, Review
from .forms import ProductForm
# Create your views here.
def all_products(request):
""" A view to show all products, including sorting and search queries """
products = Product.objects.all()
query = None
categories = None
sort = None
direction = None
if request.GET:
if 'sort' in request.GET:
sortkey = request.GET['sort']
sort = sortkey
if sortkey == 'name':
sortkey = 'lower_name'
products = products.annotate(lower_name=Lower('name'))
if sortkey == 'category':
sortkey = 'category__name'
if 'direction' in request.GET:
direction = request.GET['direction']
if direction == 'desc':
sortkey = f'-{sortkey}'
products = products.order_by(sortkey)
if 'category' in request.GET:
categories = request.GET['category'].split(',')
products = products.filter(category__name__in=categories)
categories = Category.objects.filter(name__in=categories)
if 'q' in request.GET:
query = request.GET['q']
if not query:
messages.error(request, "You didn't enter any search criteria!")
return redirect(reverse('products'))
queries = Q(name__icontains=query) | Q(description__icontains=query)
products = products.filter(queries)
current_sorting = f'{sort}_{direction}'
context = {
'products': products,
'search_term': query,
'current_categories': categories,
'current_sorting': current_sorting,
}
return render(request, 'products/products.html', context)
def product_detail(request, product_id):
""" A view to show individual product details """
product = get_object_or_404(Product, pk=product_id)
if request.method == 'POST':
rating = request.POST.get('rating', 3)
content = request.POST.get('content', '')
review = Review.objects.create(
product=product,
rating=rating,
content=content,
created_by=request.user
)
reviews = Review.objects.filter(product=product)
context = {
'product': product,
'reviews': reviews
}
return render(request, 'products/product_detail.html', context)
#login_required
def add_product(request):
""" Add a product to the store """
if not request.user.is_superuser:
messages.error(request, 'Sorry, only store owners can do that.')
return redirect(reverse('home'))
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES)
if form.is_valid():
product = form.save()
messages.success(request, 'Successfully added product!')
return redirect(reverse('product_detail', args=[product.id]))
else:
messages.error(request, 'Failed to add product. Please ensure the form is valid.')
else:
form = ProductForm()
template = 'products/add_product.html'
context = {
'form': form,
}
return render(request, template, context)
#login_required
def edit_product(request, product_id):
""" Edit a product in the store """
if not request.user.is_superuser:
messages.error(request, 'Sorry, only store owners can do that.')
return redirect(reverse('home'))
product = get_object_or_404(Product, pk=product_id)
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES, instance=product)
if form.is_valid():
form.save()
messages.success(request, 'Successfully updated product!')
return redirect(reverse('product_detail', args=[product.id]))
else:
messages.error(request, 'Failed to update product. Please ensure the form is valid.')
else:
form = ProductForm(instance=product)
messages.info(request, f'You are editing {product.name}')
template = 'products/edit_product.html'
context = {
'form': form,
'product': product,
}
return render(request, template, context)
#login_required
def delete_product(request, product_id):
""" Delete a product from the store """
if not request.user.is_superuser:
messages.error(request, 'Sorry, only store owners can do that.')
return redirect(reverse('home'))
product = get_object_or_404(Product, pk=product_id)
product.delete()
messages.success(request, 'Product deleted!')
return redirect(reverse('products'))
You should implement the Post/Redirect/Get architectural pattern [wiki], and thus return a redirect such that the browser will make a GET request next, and thus prevent making another POST request when refreshing the page:
def product_detail(request, product_id):
product = get_object_or_404(Product, pk=product_id)
if request.method == 'POST':
rating = request.POST.get('rating', 3)
content = request.POST.get('content', '')
Review.objects.create(
product=product,
rating=rating,
content=content,
created_by=request.user
)
# redirect to the same page 🖟
return redirect('product_detail', product_id=product_id)
reviews = Review.objects.filter(product=product)
context = {
'product': product,
'reviews': reviews
}
return render(request, 'products/product_detail.html', context)
Related
I keep getting this error when I add the 2 lines below in the cart app in cart/detail.html
{% item.update_quantity_form.quantity show_label=False %}
{% item.update_quantity_form.update %}
and a line below in the product app in products/product_detail.html
{% cart_product_form %}
how can I solve this error?
Invalid block tag on line 90: 'cart_product_form', expected 'endblock'. Did you forget to register or load this tag?
eshop_cart/views.py
#require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product,
quantity=cd['quantity'],
update_quantity=cd['update'])
return redirect('cart_detail')
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart_detail')
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(
initial={
'quantity': item['quantity'],
'update': True
})
return render(request, 'cart/detail.html', {'cart': cart})
eshop_products/views.py
def product_detail(request, *args, **kwargs):
selected_product_id = kwargs['productId']
product = Product.objects.get_by_id(selected_product_id)
if product is None or not product.available:
raise ("not valid")
related_product_pictures = ProductGallery.objects.filter(product_id=selected_product_id)
pictures = MainTopSlider.objects.all()
cart_product_form= CartAddProductForm()
context = {
'product': product,
'related_product_pictures': related_product_pictures,
"pictures": pictures,
'cart_product_form': cart_product_form
}
return render(request, 'products/product_detail.html', context)
eshop_products/templates/products/product_detail.html
<div class="pro-details-cart">
<form action="{% url "cart_add" product.id %}" method="post">
{% cart_product_form %}
{% buttons %}
<button type="submit" class="btn btn-success btn-product">
<span class="glyphicon glyphicon-shopping-cart"></span> Add to cart
</button>
{% endbuttons %}
</form>
eshop_cart/templates/cart/detail.html
{% for item in cart %}
{% with product=item.product %}
<tr>
<td class="product-thumbnail">
<img class="img-responsive ml-3" src="{% if product.image %}{{product.image.url }}{% else %} {% static "assets/images/no_image.png" %} {%endif %}" alt="" />
</td>
<td class="product-name">{{product.name}}</td>
<td class="product-price-cart"><span class="amount">{{ item.price }}</span></td>
<td class="product-quantity">
<form action="{% url 'cart_add' product.id %}" method="post">
{% csrf_token %}
{% item.update_quantity_form.quantity show_label=False %}
{% item.update_quantity_form.update %}
{% buttons %}
<button type="submit" class="btn btn-success btn-product">
Update
</button>
{% endbuttons %}
<button type="submit" class="btn btn-success btn-product">
Update
</button>
</form>
<div class="cart-plus-minus">
<input class="cart-plus-minus-box" type="text" name="qtybutton" value="1" />
</div>
</td>
<td class="product-subtotal">{{ item.total_price }}</td>
<td class="product-remove">
<i class="icon-pencil"></i>
<i class="icon-close"></i>
</td>
</tr>
{% endwith %}
{% endfor %}
I have problem. My template don't show my value from db.
I think that I don't have defined model UserProduct in views.py in function product.
wievs.py
def index(request):
context = {
'products': Product.objects.order_by('category').filter(is_published=True)
}
return render(request, 'offers/products.html', context)
def userproduct(request):
context = {
'userproduct': UserProduct.objects.filter(user_id=request.user.id),
}
return render(request, 'offers/userproducts.html', context)
def product(request, product_id):
product = get_object_or_404(Product, pk=product_id)
context = {
'product': product,
}
return render(request, 'offers/product.html', context)
models.py
class Product(models.Model):
product_name = models.CharField(max_length=100)
category = models.CharField(max_length=50)
weight = models.FloatField()
description = models.TextField(blank=True)
photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
is_published = models.BooleanField(default=True)
list_date = models.DateField(default=datetime.now, blank=True)
def __str__(self):
return self.product_name
class UserProduct(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
product_name = models.ForeignKey(Product, on_delete=models.CASCADE)
price = models.FloatField()
is_published = models.BooleanField(default=True)
list_date = models.DateField(default=datetime.now, blank=True)
def __str__(self):
return str(self.user.username) if self.user.username else ''
offers/product.html
<div class="p-4">
<p class="lead">
{% if user.is_authenticated %}
<span class="mr-1">
<p>Price</p></span>
<p class="colores lead font-weight-bold">{{ product.price }} £</p>
{% endif %}
<p >Description</p>
<p class="colores lead font-weight">{{ product.description }}</p>
<p class="colores lead font-weight-bold">Weight: {{ product.weight }}kg</p> </p>
{% if user.is_authenticated %}
<form class="d-flex justify-content-left">
<!-- Default input -->
<input type="number" value="1" aria-label="Search" class="form-control" style="width: 100px">
<button class="btn send-click btn-md my-0 p" type="submit">Add to cart
<i class="fas fa-shopping-cart ml-1"></i>
</button>
</form>
{% endif %}
</div>
Value product.price does not show.
The idea is that each user will have a different product price.
That's because your Product model has no price field; What you have is userproduct_set as a reverse foreign key relationship from the UserProduct model.
So you may have multiple price for one Product instance.
You can use the following code to show all the available prices for your product:
<div class="p-4">
<p class="lead">
{% if user.is_authenticated %}
<span class="mr-1">
<p>Price</p></span>
{% for userproduct in product.userproduct_set.all %}
<p class="colores lead font-weight-bold">{{ userproduct.price }} £</p>
{% endfor %}
{% endif %}
<p>Description</p>
<p class="colores lead font-weight">{{ product.description }}</p>
<p class="colores lead font-weight-bold">Weight: {{ product.weight }}kg</p> </p>
{% if user.is_authenticated %}
<form class="d-flex justify-content-left">
<!-- Default input -->
<input type="number" value="1" aria-label="Search" class="form-control" style="width: 100px">
<button class="btn send-click btn-md my-0 p" type="submit">Add to cart
<i class="fas fa-shopping-cart ml-1"></i>
</button>
</form>
{% endif %}
</div>
Read more about them in the docs.
Edit
As you mentioned in the comments, you want to find out the price assigned to the current logged in user, if the user is logged in and show weight and description even if the user is not logged in. So you need:
def product(request, product_id):
product = get_object_or_404(Product, pk=product_id)
user_product = None
if request.user.is_authenticated:
user_product = UserProduct.objects.filter(product_name_id=product_id, user=request.user)
if user_product:
user_product = user_product.first()
context = {
'product': product,
'user_product': user_product,
}
return render(request, 'offers/product.html', context)
and your template as well:
<div class="p-4">
<p class="lead">
{% if user.is_authenticated and user_product %}
<span class="mr-1">
<p>Price</p></span>
<p class="colores lead font-weight-bold">{{ user_product.price }} £</p>
{% endif %}
<p>Description</p>
<p class="colores lead font-weight">{{ product.description }}</p>
<p class="colores lead font-weight-bold">Weight: {{ product.weight }}kg</p> </p>
{% if user.is_authenticated and user_product %}
<form class="d-flex justify-content-left">
<!-- Default input -->
<input type="number" value="1" aria-label="Search" class="form-control" style="width: 100px">
<button class="btn send-click btn-md my-0 p" type="submit">Add to cart
<i class="fas fa-shopping-cart ml-1"></i>
</button>
</form>
{% endif %}
</div>
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
NoReverseMatch at /project/users/1/stories/1/
Reverse for 'user' with arguments '('',)' not found. 1 pattern(s) tried: ['project/users/(?P[0-9]+)/$']
Does anyone have any idea why I face this error when I press "python manage.py runserver"? It used to work just fine and now it just doesn't. I've seen that the problem might be with user_id or user.id, but I can't really see it! Here is my code:
project/views.py
def story(request, user_id, story_id):
if story_id is not None:
story = get_object_or_404(Story, pk=story_id)
else:
story = Story()
story.user_id = user_id
if request.method == 'POST':
story.title = request.POST['title']
story.story = request.POST['story']
story.date = timezone.now()
story.save()
return HttpResponseRedirect(reverse('project:story', args=(user_id,)))
else:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story.story,
'likes': story.likes,
'comments': story.comments
}
return render(request, 'project/story.html', context)
project/urls.py
urlpatterns = [
path('', views.index, name='index'),
path('register/<int:user_id>/', views.register, name='register'),
path('login/<int:user_id>/', views.login, name='login'),
path('users/<int:user_id>/', views.user, name='user'),
path('users/<int:user_id>/stories/<int:story_id>/', views.story, name='story'),
]
project/templates/project/story.html
{% extends "project/base.html" %}
{% block content %}
{% if story_id %}
<div class="post-preview">
<h2 class="post-title"> {{ story.title }}</h2>
<p class="post-subtitle">
{{ story.story }}
</p>
<p class="post-meta">Posted by
{{ story.author.username }}
on {{ story.date }}
<i class="fas fa-thumbs-up"> {{ story.likes }}</i>
<i class="fas fa-comment"> {{ story.comments }}</i>
</p>
</div>
<div class="post-preview">
<h2> Comments </h2>
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }}</p>
</div>
{% endfor %}
</div>
<div class="post-preview">
<form action="{% url 'project:story' user.id story.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<label for="text">Comment</label>
<textarea id="text" name="text"
class="form-control" placeholder="Comment" rows="4">{{ comment.com }}
</textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% else %}
{% if request.story.author.id == user.id %}
<form action="{% url 'project:story' user.id story.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" name="title"
class="form-control" placeholder="Title" value="{{ story.title }}"/>
</div>
<div class="form-group">
<label for="text">Story</label>
<textarea id="text" name="text"
class="form-control" placeholder="Story" rows="10">{{ story.story }}
</textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endif %}
{% endif %}
{% endblock %}
In your context we see:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story.story,
'likes': story.likes,
'comments': story.comments
}
so the story variable does not contain a Story object, it contains its story attribute (probably, based on the rest of the view, a string).
Now in your template you write:
{% url 'project:user' story.author.id %}
but since story is a string, it has no .author attribute, so this will get evaluated to the string_if_invalid which is, unless you specified otherwise, the empty string ''.
You thus need to pass the story itself in your context to:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story,
'likes': story.likes,
'comments': story.comments
}
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 %}