Currently logged in users can upvote x amount of times.
The idea of fixing it is to create a new model called Vote. And it will have foreignkeys for user and product. When someone goes to upvote, you check and see:
is there vote object with this user id and product id, and if there is, you don't allow him to upvote again; and if it is not, then you can go ahead and create that, and just increase total_votes on one.
But actually, i ran into it and cant figure it out and solve.
So, there is my models.py
from django.db import models
from django.contrib.auth.models import User
class Product(models.Model):
title = models.CharField(max_length=255)
pub_date = models.DateTimeField()
body = models.TextField()
url = models.TextField()
image = models.ImageField(upload_to='images/')
icon = models.ImageField(upload_to='images/')
votes_total = models.IntegerField(default=1)
hunter = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def summary(self):
return self.body[:100]
def pub_date_pretty(self):
return self.pub_date.strftime('%b %e %Y')
class Vote(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
voteproduct = models.ForeignKey(Product, on_delete=models.CASCADE)
And my views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from .models import Product, Vote
from django.utils import timezone
#login_required(login_url="/accounts/signup")
def upvote(request, product_id):
if request.method == 'POST':
product = get_object_or_404(Product, pk=product_id)
product.votes_total += 1
product.save()
return redirect('/products/' + str(product.id))
Updates:
class Vote(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
class Meta:
unique_together(('user', 'product'),)
views.py
if request.method == 'POST':
product = get_object_or_404(Product, pk=product_id)
try:
Vote.objects.create(user=request.user, product=product)
product.votes_total += 1
product.save()
except:
product.save()
return redirect('/products/' + str(product.id))
Also, how should i change my .html file? I really cant understand what is going on. Looking forward to hear from u.
{% for product in products.all %}
<div class="row pt-3">
<div class="col-2" onclick="window.location='{% url 'detail' product.id
%}';" style="cursor: pointer;">
<img src="{{ product.icon.url }}" class="img-fluid" />
</div>
<div class="col-7" onclick="window.location='{% url 'detail' product.id
%}';" style="cursor: pointer;">
<h1>{{ product.title }}</h1>
<p>{{ product.summary }}</p>
</div>
<div class="col-3">
<a href="javascript:{document.getElementById('{% url 'upvote'
product.id %}').submit()}"><button class="btn btn-primary btn-lg btn-
block" name="btn1" value="upvote"><span class="oi oi-caret-top">
</span> Upvote {{ product.votes_total }}</button></a>
</div>
</div>
<form id="get_redirect_url{{ product.id }}" action="{% url 'upvote'
object.id %}" method="POST">
{% csrf_token %}
<input type="hidden" />
</form>
well if i were you i would do it like that :
#step 1 change votes_total from integerfield to manytomany field
##models
class Product(models.Model):
#other fields goes here
votes_total = models.ManyToManyField(User, related_name="votes" ,)
#step 2 ; go to your views.py and copy paste the following :
from django.views.generic import RedirectView
class ProductVoteToggle(RedirectView):
def get_redirect_url(self, *args ,**kwargs):
obj = get_object_or_404(Product, pk=self.kwargs['pk'])
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated():
if user in obj.votes_total.all():
# you could remove the user if double upvote or display a message or what ever you want here
obj.votes_total.remove(user)
else:
obj.votes_total.add(user)
return url_
#step 3 : go to urls.py and paste add the following :
urlpatterns = [
#other urls here
path('vote/<int:pk>',ProductVoteToggle.as_view() , name="upvote"),
]
#in your template remove that form and paste the following code :
<a href="{% url 'upvote' product.id %}>Click to vote<a/>
when you are done it should work if it didn't past the error here and i'll let you know what you are doing wrong
Your code doesn't use Vote at all. In upvote(), first try to create a Vote for the user and product:
try:
Vote.objects.create(user=request.user, product=product)
# consider using the name product instead of voteproduct
If it succeeds, increment the counter. If it fails, notify the user that they have voted already.
Obviously, you need a unique constraint in Vote:
class Meta:
unique_together = ('user', 'product')
Related
im following a tutorial for the project but it does it on function views and im trying to do it on class based views
i get a ( The view blog.views.PostDetailView didn't return an HttpResponse object. It returned None instead.) error but thats not my concern now ... because the data(new comments) arent getting saved
so how can i save them with the post request and redirect to the same page of the DetailView
my urls
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='blog-home'),
path('blog/<slug:slug>/', views.PostDetailView.as_view() , name='post-detail'),
]
my models
class Post(models.Model):
options = (
('draft', 'Draft'),
('published', 'Published')
)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique_for_date='publish_date')
publish_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
content = models.TextField()
status = models.CharField(max_length=10, choices=options, default='draft')
class Meta:
ordering = ('-publish_date',)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'slug': self.slug})
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
publish_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-publish_date',)
def __str__(self):
return f'Comment By {self.author}/{self.post}'
my forms
class AddCommentForm(forms.ModelForm):
content = forms.CharField(label ="", widget = forms.Textarea(
attrs ={
'class':'form-control',
'placeholder':'Comment here !',
'rows':4,
'cols':50
}))
class Meta:
model = Comment
fields =['content']
my views
class PostDetailView( DetailView):
model = Post
context_object_name = 'post'
template_name='blog/post_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
comments = Comment.objects.filter(post=self.object)
context['comments'] = comments
context['form'] = AddCommentForm()
return context
def post(self, request, *args, **kwargs):
pass
def form_valid(self, form):
form.instance.author = self.post.author
user_comment.post = self.post
user_comment.save()
return super().form_valid(form)
html
<form method="POST">
<div class="col-12">
<hr>
{% with comments.count as total_comments %}
<legend class="border-bottom mb-4">{{total_comments }} comment{{total_comments|pluralize }}</legend>
{% endwith %}
{% for c in comments%}
<div class ="col-md-12 mb-1rem" >
<p class="mb-0"><strong>{{c.author}}:</strong> {{c.content}}</p>
<small class="text-muted">{{ c.publish_date|date:'f A, Y'}}</small>
</div>
<br>
{% endfor %}
</div>
<hr>
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Comment</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-dark btn-lg mt-1" type="submit">Publish</button>
</div>
</form>
The DetailView has no form_valid method.
It just shows the objects of the model.
Form processing is in the GenericEdit View class.
There are many ways, but...
In this code, you can create a GenericEdit View(CreateView or UpdateView ) url, process the form there(form_valid), and then
success_url = reverse_lazy('form:detail') # in GenericEdit View class
return the template.
To sum up,
add path in urls.py
like this... 👇
path('blog/<slug:slug>/update', views.PostUpdateView.as_view(), name='post-update'),
add update or create url in form action.
ex. ☞ action="{% url 'blog:post-update' %}"
make GenericEdit View class☞ views.PostUpdateView
ps.You can also use forms.py
"""
When you use Class Base View in django, it is recommended to refer to this site.
https://ccbv.co.uk/
and,
DetailView refer is here
https://ccbv.co.uk/projects/Django/3.0/django.views.generic.detail/DetailView/
"""
I know it's been a long time, but I think someone might need the answer in future.
I'm using django 3.2.16.
in your post method inside DetailView:
Update your post method to:
def post(self, request, *args, **kwargs):
# Get the current pk from the method dictionary
pk = kwargs.get('pk')
if request.method == 'POST':
# Get the current object
obj = self.model.objects.get(id=pk)
# Alter the field Value
some_value = request.POST.get('some_value_from_html_input')
obj.field = some_value
# Save the object
obj.save()
# Redirect to you current View after update
return redirect(current_details_view, pk=pk)
I'm having a little problem with the .save() method in Django. For 1 form it works, for the other it doesn't. And I can't find the problem.
views.py
#login_required
def stock_add(request, portfolio_id):
if request.method == 'POST':
print('request.method is ok')
form = StockForm(request.POST)
print('form is ok')
if form.is_valid():
print('form is valid')
stock = form.save(commit=False)
stock.created_by = request.user
stock.portfolio_id = portfolio_id
stock.save()
return redirect('portfolio-overview')
else:
print("nope")
else:
print('else form statement')
form = StockForm()
context = {
'form':form
}
return render(request, 'portfolios/stock-add.html', context)
forms.py
class StockForm(ModelForm):
class Meta:
model = Stock
fields = ['quote', 'amount']
html
{% extends 'core/base.html' %}
{% block content %}
<div class="container">
<h1 class="title">Add Stock</h1>
<form method="POST" action=".">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button is-primary">Submit</button>
</form>
</div>
{% endblock %}
models
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Portfolio(models.Model):
title = models.CharField(max_length=56)
description = models.TextField(blank=True, null=True, max_length=112)
created_by = models.ForeignKey(User, related_name='portfolios', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'Portfolio'
def __str__(self):
return self.title
class Stock(models.Model):
Portfolio = models.ForeignKey(Portfolio, related_name='stocks', on_delete=models.CASCADE)
quote = models.CharField(max_length=10)
amount = models.IntegerField()
created_by = models.ForeignKey(User, related_name='stocks', on_delete=models.CASCADE)
created_at = models.DateField(auto_now_add=True)
def __str__(self):
return self.quote
If you look at the views.py file, when I submit the form, it won't even do print('request.method is ok')
I can add the stock via the admin page.
So I have no clew where to look anymore...
Cheers
When you post a form and need a special url (like your' with an attribute), i like to set action="{% url myview.views.stock_add portfolio_id %}"
action="." will save to the same page without taking care of extra parameters (if needed)
Just pass portfolio_id in the context and that will work
I found the answer, an InteregerField (from models.py) needs a default value.
Either default=None (or another value).
Cheers
I have a problem in implementing DeleteView for a model that has a related model. When I try to delete a Task object nothing happens and it is not redirecting to the success_url. Noting happens. It just keeps on displaying the template.
here are the models:
class Project(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
date_created = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Task(models.Model):
name = models.CharField(max_length=50, default='New Model')
project = models.ForeignKey(Project, on_delete=models.CASCADE)
date_created = models.DateTimeField(default=timezone.now)
Here is my DeleteView Class:
class TaskDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Task
template_name = 'tasks/confirm_delete.html' # template for deletion
success_url ='/projects/'
# Test user permission
def test_func(self):
task = self.get_object()
if self.request.user == task .project.author:
return True
else:
return False
def get_success_url(self):
project = self.object.project
return reverse_lazy('tasks-listview', kwargs={'pk': project.id })
and my URL patterns:
urlpatterns = [
path('tasks/<int:pk>/list/', TasksListview.as_view(), name='tasks-listview'),
path('tasks/<int:pk>/delete/', TaskDeleteView.as_view(), name='task-delete'),
]
and here is my delete temple:
<form method=" POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class='form-group'>
<h4>Current Project: {{ object.project }}</h4>
<h4>Are you sure you want to delete task named {{ object.name }}? </h4>
</fieldset>
<div class="form-group">
<button class="btn btn-danger float-sm-right mr-1" type="submit">Yes, Delete</button>
<a class="btn btn-secondary float-sm-right mr-1" href="{% url 'task-detail' object.id %}">Cancel</a>
</div>
</form>
My delete view class contains:
def delete(self, request, *args, **kwargs):
self.get_object().delete()
messages.add_message(self.request, messages.SUCCESS, 'Entity removed with success.')
data = {'valid': True}
return JsonResponse(data)
I try to make a simple illustration of my question click to view
I have a task to make a quiz.
I try to solve a problem :
Taking items in cycle by "Metrix" model there I get Questions for Quiz
It is no way to get data from "User_metrix" model while using {% for item in metrix_list %} cycle by "Metrix" model.
My models:
from django.db import models
from django.conf import settings
class Metrix(models.Model):
title = models.CharField(max_length=256, verbose_name='Question')
metrix_category = models.ForeignKey(
'category',
related_name='Question_category',
on_delete=models.CASCADE,
verbose_name='Category',
)
is_published = models.BooleanField(default=False)
def __str__(self):
return self.title
class Category(models.Model):
title = models.CharField(max_length=256,
verbose_name='Question_category')
is_published = models.BooleanField(default=False)
def __str__(self):
return self.title
class User_metrix(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="user_metrix",
verbose_name='User')
metrix = models.ForeignKey('Metrix', on_delete=models.CASCADE,
verbose_name='Question')
value = models.DecimalField(max_digits=12, decimal_places=2,
verbose_name='Value')
My view:
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from metrix.models import Metrix, User_metrix
#login_required
def metrix_view(request, pk=None):
metrix_category = {
'pk': 4
}
#Get questions by category
metrix_list = Metrix.objects.filter(is_published=True,
metrix_category__pk=pk)
context = {
'metrix_list': metrix_list
}
return render(request, 'metrix/metrix.html', context)
Template:
I list the questions in template, by cycle "metrix_list"
How to save answers to values and if answers exists return values to template?
<!--cycle for metrix-->
{% for item in metrix_list %}
<div class="row metrix_quiestion_line justify-content-center">
<div class="metrix_quiestion">
<h2>
{{ item }}
</h2>
</div>
<div class="metrix_value">
<input type="number" name="{{ item.id }}" value=" ">
</div>
</div>
{% endfor %}
<!--END cycle -->
If I pass items to the template through the view, and I want the user to select one of the values that gets submitted to a user's record, I would only have dun a for loop in the template right?
What would that look like?
In the template:
<form method="POST"
<select>
</select>
</form>
Model:
class UserItem(models.Model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
class Item(models.Model):
name = models.CharField(max_length = 50)
condition = models.CharField(max_length = 50)
View:
def selectview(request):
item = Item.objects.filter()
form = request.POST
if form.is_valid():
# SAVE
return render_to_response (
'select/item.html',
{'item':item},
context_instance = RequestContext(request)
)
If I understood your need correctly, you can do something like:
<form method="POST">
<select name="item_id">
{% for entry in items %}
<option value="{{Â entry.id }}">{{ entry.name }}</option>
{% endfor %}
</select>
</form>
By the way, you should give the name items instead of item, since it's a collection (but it's just a remark ;)).
Doing so, you will have a list of all the items in the database.
Then, in the post, here what you need to do:
def selectview(request):
item = Item.objects.all() # use filter() when you have sth to filter ;)
form = request.POST # you seem to misinterpret the use of form from django and POST data. you should take a look at [Django with forms][1]
# you can remove the preview assignment (form =request.POST)
if request.method == 'POST':
selected_item = get_object_or_404(Item, pk=request.POST.get('item_id'))
# get the user you want (connect for example) in the var "user"
user.item = selected_item
user.save()
# Then, do a redirect for example
return render_to_response ('select/item.html', {'items':item}, context_instance = RequestContext(request),)
Of course, don't forget to include get_object_or_404
Here is a more dry way to do this in 2021:
models.py
from django.db import models
class Country(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class City(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Person(models.Model):
name = models.CharField(max_length=100)
birthdate = models.DateField(null=True, blank=True)
country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True)
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.name
urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.PersonListView.as_view(), name='person_changelist'),
path('add/', views.PersonCreateView.as_view(), name='person_add'),
path('<int:pk>/', views.PersonUpdateView.as_view(), name='person_change'),
]
views.py
from django.views.generic import ListView, CreateView, UpdateView
from django.urls import reverse_lazy
from .models import Person
class PersonListView(ListView):
model = Person
context_object_name = 'people'
class PersonCreateView(CreateView):
model = Person
fields = ('name', 'birthdate', 'country', 'city')
success_url = reverse_lazy('person_changelist')
class PersonUpdateView(UpdateView):
model = Person
fields = ('name', 'birthdate', 'country', 'city')
success_url = reverse_lazy('person_changelist')
HTML
{% extends 'base.html' %}
{% block content %}
<h2>Person Form</h2>
<form method="post" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Save</button>
Nevermind
</form>
{% endblock %}
RESULT:
Reference: https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html