Django: How do I assign a button to take info and save() - python

Is there a way for the button to do the following? : When user press the button it takes the user.username of the current user and automatically fill up a form of BookInstance from models.py and save it to the database.
From models.py :
class BookInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
book = models.ForeignKey("Book", on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200, blank=True, null=True)
due_back = models.DateField(blank=True, null=True)
borrower = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On Loan'),
('a', 'Available'),
('r', 'Reserved')
)
status = models.CharField(
max_length=1, choices=LOAN_STATUS, blank=True, default='a')
class Meta:
ordering = ['due_back']
def __str__(self):
return f'{self.id} - {self.book.title}'
def get_absolute_url(self):
return reverse("catalog:book_list")
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(
'Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(
max_length=500, help_text="Enter brief description")
isbn = models.CharField('ISBN', max_length=13, unique=True)
genre = models.ManyToManyField(Genre, help_text="Select genre")
language = models.ForeignKey(
"Language", on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("catalog:book_detail", kwargs={"pk": self.pk})
This is my from my views.py :
def borrowBook(request, pk):
context = {
'book_instance': BookInstance.objects.all()
}
success_url = reverse_lazy('catalog:index')
if request.method == "POST":
form = BorrowForm(request.POST or None)
if form.is_valid():
book_instance.id = BookInstance.objects.get(pk=pk)
book_instance.book = BookInstance.objects.get(book=book)
book_instance.borrower = request.user
book_instance.status = 'o'
book_borrowed_count = BookInstance.objects.filter(
owner=request.user).count()
if book_borrowed_count < 4:
book_instance = form.save(commit=False)
book_instance.save()
else:
print("Maximum limit reached!")
return redirect('catalog:index')
return render(request, 'catalog/book_detail.html', {'form': form})
here's from my BorrowForm from forms.py :
class BorrowForm(forms.ModelForm):
class Meta:
model = BookInstance
fields = '__all__'
here's my from my urls.py :
path("book_list/book/<int:pk>/borrow", views.borrowBook, name="borrowBook"),
I also tried using a CBV here:
class BorrowBookView(PermissionRequiredMixin, CreateView):
permission_required = 'login'
model = BookInstance
fields = '__all__'
template_name = 'catalog/borrow_form.html'
success_url = reverse_lazy('catalog:index')
def post(self, request, *args, **kwargs):
book_instance.id = BookInstance.objects.get(pk=pk)
book_instance.book = BookInstance.objects.get(book=book)
book_instance.borrower = request.user
book_instance.status = 'o'
book_instance = form.save(commit=False)
book_instance.save()
CBV path from urls.py :
path("book_list/book/<int:pk>/borrow/",
views.BorrowBookView.as_view(), name="book_borrow"),
Here's how I implemented the button using suggestions from here:
<form action="#" method="post">
{% csrf_token %}
<button
type="submit"
class="btn btn-dark flex-shrink-0 "
value="{{ book.id }}">Borrow
</button>
but when I pressed it doesn't seem to save anything to the database and just popup errors, though I may implemented the button or the function from my is views wrong. Thanks and appreciate for any help provided.

You do not need a Django form for this. Forms are usually used for when you want to create objects or edit its fields (like in the admin page). While here an user is not editing nor creating an object (book), but borrowing one.
So basically, we just need to list all available book instances (status='a'), and have a button to "borrow" it. The borrow action is to update status to 'r' or 'o' and have the borrower updated to the current user which is guaranteed to exist inside the request object by LoginRequiredMixin
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View
from django.contrib import messages
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from .models import BookInstance
class BorrowBook(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
book_id = kwargs['pk']
available_books = BookInstance.objects.filter(book__pk=book_id, status='a')
return render(request, 'borrow_book.html', {'available_books': available_books})
def post(self, request, *args , **kwargs):
book_instance_id = request.POST['id']
obj = get_object_or_404(BookInstance, id=book_instance_id)
obj.status = 'r'
obj.borrower = request.user
# Maybe also update due_back data
# obj.due_back = ...
obj.save()
messages.success(request, "Your book is reserved.")
# I used the redirection to the same template
# But you probably want to send the user somewhere else
return HttpResponseRedirect(reverse('core:borrow-book', kwargs={'pk': 1}))
borrow_book.html
{% extends 'base.html' %}
{% block content %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% for instance in available_books %}
<form action="{% url 'core:borrow-book' instance.book.id %}" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{instance.id}}">
<p>{{instance.book}}</p>
<p>{{instance.book.language.name}}</p>
<input type="submit" value="Borrow this book.">
</form>
{% endfor %}
{% endblock content %}
urls.py
from django.urls import path
from core import views
app_name = 'core'
urlpatterns = [
path("book_list/book/<int:pk>/borrow/", views.BorrowBook.as_view(), name="borrow-book"),
]

Related

Django 'x' object has no attribute 'get_form'

I have a generic DetailView and I'm trying to do a form for the comment of an user after I display the details of the model but I keep getting the error 'ProductFeedbackView' object has no attribute 'get_form'.
I don't know if the templates have any problem because the error is in the view when I try to get the form into a variable.
Here is comment's model:
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
...
def get_absolute_url(self):
return reverse('product-feedback', kwargs={'pk': self.pk})
Comment's form:
class CommentForm(forms.ModelForm):
content = forms.CharField()
class Meta:
model = Comment
fields = ['content']
View:
class ProductFeedbackView(DetailView):
model = Product
template_name = 'services/product-feedback.html'
form_class = CommentForm
def get_success_url(self):
return reverse('product-feedback', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentForm(initial={'content': self.object})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
urls's:
...
path('feedback/<int:pk>/', ProductFeedbackView.as_view(), name='product-feedback'),
Template:
Details
Feedback
<p>{{ product.author }}</p>
<h1>{{ product.title }}</h1>
<p>{{ product.description }}</p>
{% if user.is_authenticated %}
<form method="POST">
<label for="comment">Type comment</label>
{{ form.as_p }} {% csrf_token %} <input type="submit" value="Post">
</form>
{% else %}
...
{% endif %}
{% for comment in comment.service.all %}
<p>{{ comment.author }}</p>
<p>{{ comment.content }}</p>
{% endfor %}
Product model:
class Product(models.Model):
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=120, unique=True)
category = models.ForeignKey(Category, default=None, on_delete=models.PROTECT)
description = models.CharField(max_length=300, blank=True, null=True)
...
views = models.IntegerField(default=0)
featured = models.BooleanField(default=False)
date_posted = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('product-detail', kwargs={'pk': self.pk})
You need to inherit the form view mixing like so:
from django.views.generic.edit import FormMixin
class ProductFeedbackView(DetailView, FormMixin):
...
As per django classy class based views guide:
https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/FormView/
However, mixing a detail view and an edit view probably doesn't match the usual djagno-esque methodology.
Youre likely better off having a detail view and an edit view (FormView) respectively and using the built in behaviour of django.
Just add an edit button to your detail page which is a reverse to the form view :)
If you need more help, just ping a comment.

Why is the inline formset not validating

I have two models with foreign key relation
models.py
from django.db import models
# Create your models here.
class Project(models.Model):
STATUSES = (
('Ongoing', 'Ongoing'),
('Completed', 'Completed')
)
YEARS = (
(2019, 2019),
(2020, 2020),
(2021, 2021),
(2022, 2022)
)
name = models.CharField(max_length=200)
client = models.CharField(max_length=100)
year = models.SmallIntegerField(choices=YEARS)
status = models.CharField(max_length=10, choices=STATUSES)
picture = models.ImageField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class Photo(models.Model):
project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="images", blank=True, null=True)
image = models.ImageField()
description = models.CharField(max_length=100, blank=True, null=True)
slide = models.BooleanField(default=False)
I want photos and project to be created on the same form so I've used inline_formset_factory
forms.py
from django.forms import inlineformset_factory
from projects.models import Photo, Project
from django.forms import ModelForm
class ProjectModelForm(ModelForm):
class Meta:
model = Project
fields = (
'name',
'client',
'year',
'picture',
'status',
'description',
)
class PhotoModelForm(ModelForm):
class Meta:
model = Photo
fields = (
'image',
'slide',
'description'
)
PhotoFormset = inlineformset_factory(Project, Photo, form=PhotoModelForm, extra=1)
I used the generic CreateView
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from projects.forms import PhotoFormset, ProjectModelForm
from django.shortcuts import redirect, reverse, render
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from .models import Photo, Project
# Create your views here.
class ProjectCreateView(LoginRequiredMixin, CreateView):
template_name = 'projects/project_create.html'
form_class = ProjectModelForm
def get_context_data(self, **kwargs):
context = super(ProjectCreateView, self).get_context_data(**kwargs)
context['photos_formset'] = PhotoFormset()
return context
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
photo_formset = PhotoFormset(self.request.POST)
if form.is_valid() and photo_formset.is_valid():
return self.form_valid(form, photo_formset)
else:
return self.form_invalid(form, photo_formset)
def form_valid(self, form, photo_formset):
self.object = form.save(commit=False)
self.object.save()
print('valid')
photos = photo_formset.save(commit=False)
for photo in photos:
photo.project = self.object
photo.save()
return reverse('projects:projectspage')
def form_invalid(self, form, photo_formset):
if not photo_formset.is_valid():
print('invalid formset')
return self.render_to_response(
self.get_context_data(form=form, photos_formset=photo_formset)
)
def get_success_url(self):
return reverse('projects:projectspage')
this is the template
{% extends 'base.html' %}
{% load crispy_forms_filters %}
<!-- crispy_forms_tags -->
{% block content %}
<div class="container">
<h2>create new project</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<h2 class="display-6 my-5">
Add photos
</h2>
{{ photos_formset.as_p }}
<input type="submit" value="Create" class="btn btn-primary">
</form>
</div>
{% endblock content %}
When I submit form_invalid is returned from the post method of the view
How do i get the inline_formset to validate or is there better way of doing it
I prefer to write those views as functions, seems more straightforward, try:
#login_required
def post_news(request):
if request.method == 'POST':
project_form = ProjectModelForm(request.POST, request.FILES)
photo_form = PhotoModelForm(request.POST, request.FILES)
picture = request.FILES['picture']
image = request.FILES['image']
if project_form.is_valid() and photo_form.is_valid():
project_instance = project_form.save(commit=False)
project_instance.picture = picture
project_instance.save()
photo_instance = project_form.save(commit=False)
photo_instance.project = project_instance
photo_instance.image = image
photo_instance.save()
return reverse('projects:projectspage')
else:
project_form = ProjectModelForm()
photo_form = PhotoModelForm()
return render(request, 'projects/project_create.html',
{'project_form': project_form, 'photo_form': photo_form})
You will have to account for a case when no picture for the project model is is provided I think. Just pop both forms into your template and this should work.

Back button to a related object

I'm building an app where I can add recipes and add ingredients to those recipes. On view recipe_details I have a button to add_new_ingredient. When I'm on new_ingredient_form I want to have back button to get back to the details of recipe. I'm trying to pass recipe's pk but it doesn't work. How am I able to pass recipe's pk to be able to back to previous view?
models.py
class Recipe(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(max_length=100, unique=True)
preparation = models.TextField()
def __str__(self):
return self.title
class Ingredient(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
amount = models.PositiveSmallIntegerField(blank=True, null=True)
unit = models.ForeignKey('Unit', on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
views.py
class RecipeView(generic.DetailView):
model = Recipe
context_object_name = 'recipe'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ingredients_list'] = Ingredient.objects.filter(recipe=self.object.pk)
return context
class AddIngredientView(generic.edit.CreateView):
model = Ingredient
fields = [
'name',
'amount',
'unit'
]
success_url = '/'
template_name = 'recipes/add_ingredient.html'
def dispatch(self, request, *args, **kwargs):
self.recipe = get_object_or_404(Recipe, pk=self.kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.recipe = self.recipe
return super().form_valid(form)
def get_success_url(self):
if 'add_another' in self.request.POST:
url = reverse_lazy('recipes:add_ingredient', kwargs={'pk': self.object.recipe_id})
else:
url = reverse_lazy('recipes:recipe', kwargs={'pk': self.object.recipe_id})
return url
add_ingredient.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Save</button>
<button class="btn btn-success" type="submit" name="add_another">Save and add another</button>
Back
</form>
{% endblock %}
You can go to the previous page by adding {{request.META.HTTP_REFERER}} to the href property of the button.
Django request to find previous referrer

Django version 3.1.3 form not saving to model

I am following this tutorial
I have gone back and written the code to match exactly. I have another form that works called category_add which is exactly the same as this form. But for the life of me I cannot figure out why bookmark_add doesn't update the database with the form entries.
Models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
created_by = models.ForeignKey(User, related_name='categories', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.title
class Bookmark(models.Model):
category = models.ForeignKey(Category, related_name='bookmarks', on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
url = models.CharField(max_length=255, blank=True)
created_by = models.ForeignKey(User, related_name='bookmarks', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
View.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .forms import BookmarkForm
#login_required
def bookmark_add(request, category_id):
if request.method == 'POST':
form = BookmarkForm(request.POST)
if form.is_valid():
bookmark = form.save(commit=False)
bookmark.created_by = request.user
bookmark.category_id = category_id
bookmark.save()
return redirect('category', category_id=category_id)
else:
form = BookmarkForm()
context = {
'form': form
}
return render(request, 'bookmark/bookmark_add.html', context)
Forms.py
from django.forms import ModelForm
from .models import Bookmark
class BookmarkForm(ModelForm):
class Meta:
model = Bookmark
fields = ['title', 'description', 'url']
Urls.py
path('', dashboard, name='dashboard'),
path('categories/', categories, name='categories'),
path('categories/add/', category_add, name='category_add'),
path('categories/<int:category_id>/', category, name='category'),
path('categories/<int:category_id>/add_bookmark', bookmark_add, name='bookmark_add')
]
bookmark_add.html
{% extends 'core/base.html' %}
{% block content %}
<div class="container">
<h1 class="title">Add link</h1>
<form method="post" action=".">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button is-primary">Submit</button>
</form>
</div>
{% endblock %}
Solved this!
This was a dumb issue and an oversight on my end. Thanks to the content creator on youtube. I just needed to append "/" to the url path for add_bookmark.
Problem:
path('categories/<int:category_id>/add_bookmark', bookmark_add, name='bookmark_add')
The Fix:
path('categories/<int:category_id>/add_bookmark/', bookmark_add, name='bookmark_add')

Getting an error whenever i click on add to cart button

i created a cart app inside an ecommerce site am building but when i click on
the add cart form that is rendered on my page, it doesn't come through. any
help will be appreciated
inside the cart app, i have the normal django files in it.(__init__.py,
admin.py, apps.py, cart.py (added by me), models.py, tests.py, urls.py and views.py)
Here are the content of files in my cart app that i filled with content.
for
cart.py:
from cart.models import CartItems
from catalog.models import Product
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
import decimal
import random
CART_ID_SESSION_KEY = 'cart_id'
# get the current user's cart id, sets new one if blank
def _cart_id(request):
if request.session.get(CART_ID_SESSION_KEY,'') == '':
request.session[CART_ID_SESSION_KEY] = _generate_cart_id()
return request.session[CART_ID_SESSION_KEY]
def _generate_cart_id():
cart_id = ''
characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!##$%^&*()'
cart_id_length = 50
for y in range(cart_id_length):
cart_id += characters[random.randint(0, len(characters)-1)]
return cart_id
# return all items from the current user's cart
def get_cart_items(request):
return CartItems.objects.filter(cart_id=_cart_id(request))
# add an item to the cart
def add_to_cart(request):
postdata = request.POST.copy()
# get product slug from post data, return blank if empty
product_slug = postdata.get('self.slug','')
# get quantity added, return 1 if empty
quantity = postdata.get('quantity',1)
# fetch the product or return a missing page error
p = get_object_or_404(Product, slug=product_slug)
#get products in cart
cart_products = get_cart_items(request)
product_in_cart = False
# check to see if item is already in cart
for cart_item in cart_products:
if cart_item.product.id == p.id:
# update the quantity if found
cart_item.augment_quantity(quantity)
product_in_cart = True
if not product_in_cart:
# create and save a new cart item
ci = CartItems()
ci.product = p
ci.quantity = quantity
ci.cart_id = _cart_id(request)
ci.save()
# returns the total number of items in the user's cart
def cart_distinct_item_count(request):
return get_cart_items(request).count()
models.py:
from django.db import models
from catalog.models import Product
class CartItems(models.Model):
cart_id = models.CharField(max_length=50)
date_added = models.DateTimeField(auto_now_add=True)
quantity = models.IntegerField(default=1)
product = models.ForeignKey(Product, unique=False,
on_delete=models.CASCADE)
class Meta:
db_table = 'cart_items'
ordering = ['date_added']
def total(self):
return self.quantity * self.product.price
def name(self):
return self.product.name
def price(self):
return self.product.price
def get_absolute_url(self):
return self.product.get_absolute_url
urls.py:
from django.urls import path
from cart import views
urlpatterns = [
path('', views.show_cart, name='show_cart')
]
views.py:
from django.shortcuts import render
from django.template import RequestContext
from cart import cart
def show_cart(request):
cart_item_count = cart.get_cart_items(request)
context_instance = RequestContext(request)
page_title = "showing Cart"
context = {'page_title' : page_title,
'cart_item_count': cart_item_count,
'context_instance': context_instance
}
template = 'cart/cart.html'
return render(request, template, context)
Below are the models.py, views.py and forms.py of another app(catalog)
in the project that have inheritance of the cart app
models.py:
from django.db import models
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=50, db_index=True)
slug = models.SlugField(max_length=50, unique=True, help_text="Unique
value for product page URL, created from name.")
description = models.TextField(blank=False)
is_active = models.BooleanField(default=True)
meta_keywords = models.CharField("Meta Keywords", max_length=255,
help_text="Comma-delimited set of SEO keywords for meta tag")
meta_description = models.CharField("Meta Description", max_length=255,
help_text="Content for description meta tag")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "category"
ordering = ["-created_at"]
verbose_name_plural = "Categories"
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:show_category', args=[self.slug])
class Product(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, unique=True, help_text="Unique
value for product page URL, created from name.")
brand = models.CharField(max_length=50)
sku = models.CharField(max_length=50)
price = models.DecimalField(max_digits=9, decimal_places=2)
old_price = models.DecimalField(max_digits=9, decimal_places=2,
blank=True, default=0.00)
image = models.ImageField(upload_to='media', blank=True)
is_active = models.BooleanField(default=True)
is_bestseller = models.BooleanField(default=False)
is_featured = models.BooleanField(default=False)
quantity = models.IntegerField()
description = models.TextField(blank=False)
meta_keywords = models.CharField(max_length=255, help_text = 'Comma-
delimited set of SEO keywords for meta tag')
meta_description = models.CharField(max_length=255, help_text = 'Content
for description meta tag')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField(Category)
class Meta:
db_table = 'products'
ordering = ['-created_at']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('catalog:show_product', args=[self.slug])
def sale_price(self):
if self.old_price > self.price:
return self.price
else:
return None
views.py:
from django.shortcuts import render, get_object_or_404, redirect
from catalog.models import Category, Product
from django.template import RequestContext
from django.urls import reverse
from cart import cart
from django.http import HttpResponseRedirect
from catalog.forms import ProductAddToCartForm
def index(request):
context_instance = RequestContext(request)
template = "catalog/index.html"
page_title = 'Musical Instruments and Sheet Music for Musicians'
context = {'page_title': page_title,
'context_instance': context_instance}
return render(request, template, context)
def show_category(request, category_slug):
template = "catalog/category.html"
context_instance = RequestContext(request)
c = get_object_or_404(Category, slug=category_slug)
products = c.product_set.all()
page_title = c.name
meta_keywords = c.meta_keywords
meta_description = c.meta_description
context = {
'context_instance': context_instance,
'c': c,
'products': products,
'page_title': page_title,
'meta_keywords': meta_keywords,
'meta_description': meta_description,
}
return render(request, template, context)
def show_product(request, product_slug):
p = get_object_or_404(Product, slug=product_slug)
context_instance = RequestContext(request)
categories = p.categories.filter(is_active=True)
page_title = p.name
meta_keywords = p.meta_keywords
meta_description = p.meta_description
if request.method == 'POST':
postdata = request.POST.copy()
form = ProductAddToCartForm(request, postdata)
if form.is_valid():
cart.add_to_cart(request)
if request.session.test_cookie_worked():
request.session.delete_test_cookies()
return redirect('cart: show_cart')
else:
form = ProductAddToCartForm(request=request, label_suffix=':')
form.fields['product_slug'].widget.attrs['value'] = product_slug
request.session.set_test_cookie()
context = {
'context_instance': context_instance,
'p': p,
'categories': categories,
'page_title': page_title,
'meta_keywords': meta_keywords,
'meta_description': meta_description,
'form':form
}
template = "catalog/product.html"
return render(request, template, context)
urls.py:
from django.urls import path
from catalog import views
app_name = 'catalog'
urlpatterns = [
path('', views.index, name='index'),
path('<category_slug>/', views.show_category, name='show_category'),
path('product/<product_slug>', views.show_product, name='show_product'),
]
template that the form is embedded in
{% extends "catalog.html" %}
{% block content %}
{% block sidebar %}
{% endblock %}
<div class="container mt-2 pl-5">
<div class="row text-justify ml-5 pl-5">
<div class="col-md img-responsive">
<img src="{{ p.image.url }}" alt="{{ p.name }}" class="img-responsive
center-block img-thumbnail" width="100%"/>
</div>
<div class="col-md">
<h4>{{ p.name }}</h4>
Brand: <em>{{ p.brand }}</em>
<br /><br />
SKU: {{ p.sku }}
<br />
In categor{{ categories.count|pluralize:"y,ies" }}:
{% for c in categories %}
{{ c.name }}
{% if not forloop.last %}, {% endif %}
{% endfor %}
<br /><br />
{% if p.sale_price %}
Was: <s>$ {{ p.old_price }}</s>
<br />
Now: $ {{ p.price }}
{% else %}
Price: $ {{ p.price }}
{% endif %}
<br /><br />
<form method="POST" action=".">
{{ form.as_p }}
{% csrf_token %}
<br />
<input type="submit" value="Add To Cart" name="submit" alt="Add To Cart"
/>
</form>
</div>
</div>
<div class=" text-center pt-5 mb-5 pb-5">
<h3>Product Description</h3>
{{ p.description }}
</div>
</div>
{% endblock %}
whenever i click the add to cart, i get this error:
Page not found (404)
Request Method: POST
Request URL:http://127.0.0.1:8000/catalog/product/
Raised by:catalog.views.show_category
As you can see in your error message
Raised by:catalog.views.show_category
catalog.views.show_category raised the error, so since it is a 404 it is probably raised by
c = get_object_or_404(Category, slug=category_slug)
Now, it is trying to search a category... Why? The url is http://127.0.0.1:8000/catalog/product/
Let's check your paths
path('<category_slug>/', views.show_category, name='show_category'),
Interesting, so any path followed by a slash calls show_category! Not what you would want, that means that http://127.0.0.1:8000/catalog/product/ is trying to show a category with slug name 'product'!!
Now there are multiple ways to fix it. All of them involve restructuring your paths (and changing the rest of the application accordingly)

Categories