Hello guys I am making a ecommerce website as part of learning django. I have a review model but I want to make sure that a customer can only put a review once. How to do that with my existing model and views. Please have a look at my codes:
views
def product_review(request, slug):
user = request.user
product = get_object_or_404(Product, slug=slug)
reviews = ProductFeedback.objects.filter(product=product).order_by('-id')
if request.POST:
if product.user == request.user:
messages.error(request, 'You can\'t review your own product')
return redirect('store:product_detail', product.slug)
else:
p_review_form = ProductReviewForm(request.POST or None)
if p_review_form.is_valid():
product_rating = request.POST.get('product_rating')
product_review = request.POST.get('product_review')
p_review = ProductFeedback.objects.create(product=product, user=user,
product_rating=product_rating, product_review=product_review)
p_review.save()
return redirect('store:product_detail', product.slug)
else:
p_review_form = ProductReviewForm()
context = {
'product':product,
'p_review_form':p_review_form,
'reviews': reviews,
}
return render(request, 'store/product/product_detail.html', context)
model
class ProductFeedback(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='product')
product_rating = models.CharField(choices=RATINGS, max_length=10, null=True)
product_review = models.TextField(max_length=1000)
reply = models.ForeignKey('ProductFeedback', null=True, related_name='replies', blank=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.user.username}--{self.product.title} review'
You can filter all reviews by the user if there is any review then raise an error. The main idea is to get the reviews and filter them by request.user and see if there are any.
from django.core.exceptions import PermissionDenied
def product_review(request, slug):
product = get_object_or_404(Product, slug=slug)
# Get the reviews posted by the user for this product
user_review = Product.product.filter(user=request.user)
if request.method == 'POST':
if user_review:
# If there is/are any reviews, raise an error
raise PermissionDenied('You have already given your review on this post.')
Hope this helps.
Related
I am making a todo app for practice, the functionality I want to achieve is that, when a user create a task,Only the time field should be unique about all of his tasks, I have done (unique=True) In the time field in model but that make it unique all over the database, but I want it to be unique only with the tasks associated with the user.
the view is below:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
task model:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.TimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
def __str__(self):
return self.task
def get_task_author_profile(self):
return reverse('profile')
as you can see, I want to show the task that the logged in user has added.
the form is:
class TaskForm(ModelForm):
class Meta:
model = Task
fields = '__all__'
exclude = ['name']
the functionality I talked about above, I tried to achieve through view:
#login_required(login_url='login')
def home(request):
tasks = Task.objects.filter(name__username=request.user.username)
time = []
for task in tasks:
time.append(task['time'])
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid() and form.cleaned_data['time'] != time:
obj = form.save(commit=False)
obj.name = request.user
obj.save()
return redirect('home')
else:
print(request.POST)
print(request.user.username)
messages.warning(request, 'Invalid Data!')
return redirect('home')
context = {'tasks' : tasks}
return render(request, 'task/home.html', context)
but that gave an error: TypeError: 'Task' object is not subscriptable
I know its not right, but how can I achieve it, does Django have anything that can provide such fuctionality?
The problem is coming from here:
for task in tasks:
time.append(task['time']) #<--
Here if you want to use access time, you need to use task.time because task is an object.
Also need to fix another thing in your exisiting code to make it work, because time is a list:
if form.is_valid() and form.cleaned_data['time'] in time:
# ^^^
BTW, you don't need to make it that complicated, you can add Database level constraint from the model to make the times unique for a specific user. Also, use DateTime field for that. You can use unique_togather for that:
class Task(models.Model):
choices = (
('Completed', 'Completed'),
('In Complete', 'In Complete'),
)
name = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
task = models.CharField(max_length=200, null=False, blank=False)
time = models.DateTimeField(auto_now_add=False, blank=True)
status = models.CharField(max_length=200, choices=choices, null=True, blank=False)
class Meta:
unique_togather = ['name', 'time']
First post here. I followed the project code in the Python Crash Course book, so if the code looks similiar that is why.
In my django project, I want all the users to be able to view everything on the website. However, I want to have only the users who entered information in the entries to be able to edit and delete said entries. I know I need to assign ownership, but I'm having difficulty assigning it. currently I have a default setting enabled. For some reason if I take out the default it messes everything up.
urls.py
"""Defines URL patterns for research_topic_database."""
# research_topic_database
from django.urls import path
from . import views
app_name = 'research_topic_database'
urlpatterns = [
# Home page
path('', views.index, name='index'),
# Page that shows all the research category topics.
path('topics/', views.topics, name='topics'),
# Detail page for a single research entry submission.
path('topics/<int:topic_id>/', views.topic, name='topic'),
# Page for adding a new research entry submission.
path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
# Page for editing an entry.
path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
]
models.py
class Topic(models.Model):
"""A research category for topic submissions to fall under."""
category = models.CharField(max_length=19, choices=CATEGORY_CHOICES, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a string representation of the model."""
return self.category
class Entry(models.Model):
"""Entry information about a research topic."""
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
classification = models.CharField(max_length=10, choices=CLASSIFICTION_CHOICES, blank=True)
# The research_title identifies the entry in the database.
research_title = models.CharField(max_length=200)
research_topic_description = models.CharField(max_length=200)
# Explain the USMC requirements you want the research to fulfill.
extended_topic_description = models.TextField()
desired_objectives = models.TextField()
sponsor = models.CharField(max_length=100)
point_of_contact = models.CharField(max_length=100)
mailing_address = models.CharField(max_length=200)
email_address = models.CharField(max_length=50)
phone = models.CharField(max_length=12)
fax = models.CharField(max_length=12)
desired_completion_date = models.CharField(max_length=12)
available_funding = models.CharField(max_length=50)
comments = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
# connecting research entries to certain Users.
owner = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
# owner = models.ForeignKey(User, on_delete=models.CASCADE)
# owner = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model."""
return f"{self.research_title[:100]}..."
views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404
from .models import Topic, Entry
from .forms import EntryForm
def index(request):
"""The home page for research_topic_database."""
return render(request, 'research_topic_database/index.html')
#login_required
def topics(request):
"""Show all research categories from TOPIC class."""
topics = Topic.objects.order_by('date_added')
context = {'topics': topics}
return render(request, 'research_topic_database/topics.html', context)
#login_required
def topic(request, topic_id):
"""Show a single research category from TOPIC class
and all its submission entries."""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'research_topic_database/topic.html', context)
#login_required
def new_entry(request, topic_id):
"""Add a new entry submission under a research category."""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
# No data submitted; create a blank form.
form = EntryForm()
else:
# POST data submitted; process data.
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return redirect('research_topic_database:topic', topic_id=topic_id)
# Display a blank or invalid form
context = {'topic': topic, 'form': form}
return render(request, 'research_topic_database/new_entry.html', context)
#login_required
def edit_entry(request, entry_id):
"""Edit an existing research entry submission."""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
# Initial request, pre-fill form with current entry.
form = EntryForm(instance=entry)
else:
# POST data submitted; process data.
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return redirect('research_topic_database:topic', topic_id=topic.id)
context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'research_topic_database/edit_entry.html', context)
I am trying to get user/customer info and copy it automatically to new model via custom form once requested but keep getting error. General overview as below.
(error : ValueError at /create_request/
Cannot assign "<SimpleLazyObject: <User: kambing>>": "OrderRequest.user" must be a "Customer" instance.)
model relationship between customer model and request model
my views.py as below:
from django.shortcuts import render, redirect
from .models import *
from accounts.models import *
from .forms import CreateRequestForm
from django.contrib.auth.decorators import login_required
from accounts.models import *
#login_required(login_url='login')
def createRequest(request):
form = CreateRequestForm()
if request.method =='POST':
form = CreateRequestForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.user = request.user
order.save()
return redirect('/')
context = {'form':form}
return render(request, 'commerce_autoparts/request_form.html', context)
here my model.py(autopart_ecommerce): Same as shown in attached image
from django.db import models
from accounts.models import Customer
class Logistic(models.Model):
logistic_method = models.CharField(max_length=30, null=True,
blank=True)
def __str__(self):
return self.logistic_method
class OrderRequest(models.Model):
user = models.ForeignKey(Customer, on_delete=models.CASCADE,
null=True, blank=True)
ref_code = models.CharField(max_length=15, null=True, blank=True)
link = models.URLField(null=True, blank=True)
image = models.ImageField(upload_to='media/images',null=True,
blank=True)
price = models.FloatField(null=True)
draft = models.BooleanField(default=True)
logistic_method = models.ForeignKey(Logistic,
on_delete=models.CASCADE, null=True, blank=True)
note = models.TextField(max_length=100)
date_order = models.DateTimeField(auto_now=True)
I tried many solutions from stackoverflow but could not get it right. I am sorry if this not challenging question but I am stuck with this issue for some weeks. Some sort of guidance or reference available would be helpful.
I hope shared info is enough for your references.
Thank you
The problem is that it is a different model, so it doesn't like that. What you can do is copy the field data of the user to the customer, so you actually create new customer.
example:
# instead of this
order.user = request.user
order.save()
# you could do
new_user = Customer()
new_user.name = request.user.name
new_user.email = request.user.email
new_user.last_name = request.user.last_name
# then pass the user to the order
order.user = new_user
order.save()
I somehow manage to solve the issue. It was simply i don't refer the user to customer model. By adding the customer model (instance.user = request.user.customer), it automatically save the customer id once submitted. it may not be the perfect solution, but works for now..
#login_required(login_url='login')
def createRequest(request):
form = CreateRequestForm(request.POST)
if request.method =='POST':
form = CreateRequestForm(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user.customer
instance.ref_code = get_ref_code()
instance.save()
return redirect('/')
I would like to create a model that contains a timestamp and the allauth currently logged in user who agreed to the Terms of Service. Then, on every page (if the user is logged in), annotate if the user has agreed to the latest Terms of Service (by comparing the timestamp of their last agreement to the timestamp of the latest updated Terms of Service), and if the user has not agreed to the most recent Terms of Service version they are redirected to a page that requires them to agree to the updated version. Then it redirects the user back to whence they came after they agree.
How does one go about creating something like this?
What I have so far is below.
Models.py:
from django.contrib.auth.models import User
class TermsOfService(models.Model):
agreement = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
def __str__(self):
return self.agreement
class UserMembership(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
stripe_customer_id = models.CharField(max_length=40, unique=True)
membership = models.ForeignKey(
Membership, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.user.username
Forms.py:
from .models import TermsOfService
class TermsOfServiceForm(forms.ModelForm):
class Meta:
model = TermsOfService
fields = ('agreement',)
def __init__(self, *args, **kwargs):
super(TermsOfServiceForm, self).__init__(*args, **kwargs)
self.fields['agreement'].widget.attrs={ 'id': 'agreement_field', 'class': 'form-control', 'required': 'true', 'autocomplete':'off'}
App Urls.py:
from django.urls import path
from .views import ( terms_of_service_view )
app_name = 'app'
urlpatterns = [ path('terms_of_service_view/', terms_of_service_view, name='terms_of_service_view'), ]
Views.py:
def get_user_membership(request):
user_membership_qs = UserMembership.objects.filter(user=request.user)
if user_membership_qs.exists():
return user_membership_qs.first()
return None
def terms_of_service_view(request):
if request.method == 'POST':
form = TermsOfServiceForm(request.POST)
if form.is_valid():
user_membership = get_user_membership(request)
instance = form.save(commit=False)
instance.user = request.user
instance.save()
context = {
'user_membership': user_membership,
'form':form
}
return render(request, "index.html", context)
else:
form = TermsOfServiceForm()
context = {
'user_membership': user_membership,
'form': form,
}
return render(request, "index.html", context)
A question arises from your code, like how are you going to determine when user needs to agree to agreement, do you create a bunch of new entry in TermsOfService. Rather than that, why not create a new model named Terms and add it as ForeignKey.
class Term(models.Model):
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
# blah blah
class TermsOfService(models.Model):
term = models.ForeignKey(Term, on_delete=models.DO_NOTHING)
agreement = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True,blank=True, null=True)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
There is an advantage of taking this approach, that is all you need to do is create a new Term object, and rest can be taken care of by middleware. For example:
from django.urls import reverse
from django.shortcuts import redirect
class TermAgreeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if not request.user.is_authenticated:
return response
term_date = Term.objects.last().created_at
user_term_date = request.user.termofservice_set.filter(created_at__gte=term_date).exists()
if not user_term_date:
return redirect(reverse('app:terms_of_service_view')+'?next='+request.path)
return response
And update the view:
def terms_of_service_view(request):
if request.method == 'POST':
form = TermsOfServiceForm(request.POST)
if form.is_valid():
user_membership = request.user.usermembership # you don't need another view as User has OneToOne relation with UserMembership
instance = form.save(commit=False)
instance.user = request.user
instance.term = Term.objects.last()
instance.save()
go_next = request.GET.get('next', None) # handle redirection
if go_next:
return redirect(go_next)
context = {
'user_membership': user_membership,
'form':form
}
return render(request, "index.html", context)
else:
form = TermsOfServiceForm()
context = {
'user_membership': user_membership,
'form': form,
}
return render(request, "index.html", context)
Finally add that TermAgreeMiddleware in MIDDLEWARE settings. So everytime you want users to agree a new term, just create a new Term instance(from admin site or shell).
I have a "product" field that I want to use to determine which form to display. I am trying to do this in the view but wondering if I should do it in the template instead. I have tried the following but "form" does not get assigned by my if statements. What am I doing wrong?
#login_required
def update_message(request, pk):
message = get_object_or_404(Submission, pk=pk)
author = message.author
date_posted = message.date_posted
product = message.product
message_obj = Submission.objects.get(pk=pk)
program_type = message.program_type
if author == request.user:
if request.method == 'POST':
if product == 'Apple':
form = AppleForm(request.user, request.POST, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, request.POST, instance=message)
if form.is_valid():
message_sub = form.save(commit=False)
message_sub.author = request.user
message_sub.date_posted = timezone.now()
message_sub.save()
form.save_m2m()
messages.success(request, 'Message updated')
return redirect('submission-list')
else:
if product == 'Apple':
form = AppleForm(request.user, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, instance=message)
else:
messages.warning(request, 'You can't do that.')
return redirect('message-submission-list')
return render(request, 'programs/submission_create_form.html', {'product':product,'form': form, 'message_obj': message_obj,'program_type':program_type})
class MessageSubmission(models.Model):
message = models.CharField(max_length=5000)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
program_code = models.ManyToManyField(Program)
program_type = models.CharField(max_length=30, blank=True)
product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
production_cycle = models.ManyToManyField('ProductionCycle', null=True)
def get_absolute_url(self):
return reverse('submission-list')
def __str__(self):
return self.message
As I mentioned in the comment, the issue is that product is a ForeignKey to another model. In the template, the FK will display using the __str__ method of that model, but that doesn't make it equal to that display value. You should compare explicitly with the relevant field on the target model:
if product.fruit_type == 'Orange' # or whatever the field is
(Alternatively you could do if str(product) == 'Orange' but that's more brittle and is coupling display logic in a way that's not very nice.)
There's nothing wrong with doing this in the views. If the form is not defined after those if statements then it means that the value of product is not Apple or Orange, but something else. I would double check the value of product to fix the issue.
Since Product is a class, you should reference a field. You didn't post the code for it, but for example
if form == product.name
If there is a name field.