I have a three simple models. User, Restaurant, Menu. A single user can have many restaurants, and each restaurant can have many Menus. I am using the generic Django CreateView for the Posting of the Restaurants and Menus to their respective models. This works for The restaurants, But fails for the Menus and I get a "Restaurant matching query does not exist" ERROR. I feel that it is a URL issue because I can reach the template if I input it manually, but fails when i use the button.
VIEWS.py
#login_required
def profile(request, pk):
user = CustomUser.objects.get(pk=pk)
if not user.id == request.user.pk:
raise PermissionDenied()
else:
context = {
'user': user,
'rests': Restaurant.objects.filter(account=request.user).order_by('-date'),
'countRests': Restaurant.objects.filter(account=request.user).count(),
}
return render(request, 'restaurants/profile.html',context)
class RestCreateView(LoginRequiredMixin, CreateView):
template_name = 'restaurants/makeRestaurant.html'
form_class = RestCreateForm
def form_valid(self, form):
form.instance.account = self.request.user
return super().form_valid(form)
def RestMenus(request, pk, name):
rest = Restaurant.objects.get(name=name)
user = CustomUser.objects.get(pk=pk)
if not user.id == request.user.pk:
raise PermissionDenied()
else:
context = {
'rest': rest,
'user': user,
'menus': Menu.objects.filter(restaurant=rest).order_by('-uploadDate'),
'countMenus': Menu.objects.filter(restaurant=rest).count(),
}
return render(request, 'restaurants/menus.html',context)
class MenuCreateView(LoginRequiredMixin, CreateView):
template_name = 'restaurants/makeMenu.html'
form_class = MenuCreateForm
def form_valid(self, form):
form.instance.restaurant = self.request.restaurant
return super().form_valid(form)
URLS.py
urlpatterns = [
path('', home, name='home'),
path('profile/<int:pk>/',profile, name='profile'),
path('profile/<int:pk>/createRest/', RestCreateView.as_view(), name='createRest'),
path('profile/<int:pk>/restaurant/<str:name>',RestMenus, name='RestDetail'),
path('profile/<int:pk>/restaurant/<str:name>/createMenu/', MenuCreateView.as_view(), name='createMenu'),
]
FORMS.py
class RestCreateForm(forms.ModelForm):
class Meta:
model = Restaurant
fields = [
'name'
]
class MenuCreateForm(forms.ModelForm):
class Meta:
model = Menu
fields = [
'name',
'menuFile'
]
Template (Menus.html)
<section id="menusdash">
<div class="container">
<br>
<h3>{{ rest.name }}'s Menus</h3>
<br>
<div class="row">
<div class="col-md-10">
{% if countMenus == 0 %}
<p>You have no Menus</p>
{% else %}
{% for menu in menus %}
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Menu Name: {{ menu.name }}</h5>
View Menu
View QR Code
Edit
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class="col-md-2">
<button>Make a Menu</button>
</div>
</div>
</div>
</section>
The URL seems to drop the str:name/ for the menu create template. Is it possible to pass a second argument using a CreateView, SO that i can have the PK and String:name in the url?
Related
I got problem with my edit comments when i press the edit comment from the template i get no error but is redirected to the top of the same page. Anyone got any idea how i can get it to allow me to edit the comment?
This post is edited with only the code needed and i fixed the delete comment from before so that code is removed.
Here is my code:
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, ReviewForm
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)
#login_required
def edit_review(request, review_id):
"""
Saves review form edited by user
"""
review = get_object_or_404(Review, pk=review_id)
product = Product.objects.get(name=review.product)
if request.method == 'POST':
review_form = ReviewForm(request.POST or None, instance=review)
if review_form.is_valid():
review_form.save()
messages.success(request, 'Successfully updated product!')
return redirect(reverse('product_detail', args=[product.id]))
# Success message if added
messages.success(request, 'Thank You! Review was edited')
else:
# Error message if form was invalid
messages.error(request, 'Something went wrong. '
'Make sure the form is valid.')
form = ReviewForm(instance=review)
messages.info(request, f'You are editing {review_id}')
template = 'products/edit_review.html'
context = {
'form': form,
'product': review,
}
return redirect(reverse('product_detail', args=[product.id]))
models.py:
from django.db import models
from django.contrib.auth.models import User
class Review(models.Model):
product = models.ForeignKey(Product, related_name='reviews', on_delete=models.CASCADE)
rating = models.IntegerField(default=3)
content = models.TextField()
created_by = models.ForeignKey(User, related_name='reviews', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s' % (self.product.name, self.created_by)
urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.all_products, name='products'),
path('<int:product_id>/', views.product_detail, name='product_detail'),
path('add/', views.add_product, name='add_product'),
path('edit/<int:product_id>/', views.edit_product, name='edit_product'),
path('delete/<int:product_id>/', views.delete_product, name='delete_product'),
path('delete_review/<int:review_id>/delete_review', views.delete_review, name='delete-review'),
path('edit_review/<review_id>', views.edit_review, name="edit_review"),
]
forms.py:
from django import forms
from .widgets import CustomClearableFileInput
from .models import Product, Category, Review
class ReviewForm(forms.ModelForm):
class Meta:
model = Review
fields = ('content', 'rating')
widgets = {
'content': forms.Textarea(attrs={'class': 'form-control'}),
'rating': forms.Select(attrs={'class': 'form-control'}),
}
edit_review.html template
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="overlay"></div>
<div class="container">
<div class="row">
<div class="col-12 col-md-6">
<hr>
<h2 class="logo-font mb-4">Reviews</h2>
<h5 class="text-muted">Edit you're Review</h5>
<hr>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<form method="POST" action="{% url 'edit_review' review.id %}" class="form mb-2" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
{% if field.name != 'image' %}
{{ field | as_crispy_field }}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
<div class="text-right">
<a class="btn btn-outline-black rounded-0" href="{% url 'reviews' %}">Cancel</a>
<button class="btn btn-black rounded-0" type="submit">Update Review</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
for the first problem in the delete view you must refer to the model with capital Review
#login_required
def delete_review(request, review_id):
review = Review.objects.filter(review_id).last()
For the second problem you can see detailed information about this error and how to handle it, from Here
I am learning Python and Django, and I am trying to create a recipe with a form. The problem is I have two models, Recipe and RecipeIngredient and I don't know how to add recipe ingredients to the form since it requires the primary key of the recipe and from what I understand, the key is not created until after the form is saved? So how can I create a recipe with both the Recipe and RecipeIngredient when Recipe is not yet initialized?
Models.py:
class Recipe(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(upload_to='image/', blank=True, null=True)
name = models.CharField(max_length=220) # Lasanga
description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True)
cookTime = models.CharField(max_length=50, blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
#property
def title(self):
return self.name
def get_absolute_url(self):
return reverse("recipes:detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_hx_url(self):
return reverse("recipes:hx-detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_edit_url(self):
return reverse("recipes:update", kwargs={"id": self.id})
def get_image_upload_url(self):
return reverse("recipes:recipe-ingredient-image-upload", kwargs={"parent_id": self.id})
def get_delete_url(self):
return reverse("recipes:delete", kwargs={"id": self.id})
def get_ingredients_children(self):
return self.recipeingredient_set.all()
def get_instruction_children(self):
return self.recipeinstruction_set.all()
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=220) # grilled chicken pasta
description = models.TextField(blank=True, null=True)
quantity = models.CharField(max_length=50, blank=True, null=True)
unit = models.CharField(max_length=50, validators=[validate_unit_of_measure], blank=True, null=True)
instructions = models.TextField(blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
def get_absolute_url(self):
return self.recipe.get_absolute_url() # recipe cannot be none
def get_hx_edit_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:hx-ingredient-detail", kwargs=kwargs)
def get_delete_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:ingredient-delete", kwargs=kwargs)
Views.py
#login_required
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
context = {
"form": form,
}
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
if request.htmx: # necessary to pass headers from htmx response if we want the django to recognise the htmx change
headers = {
"HX-Redirect": obj.get_absolute_url()
}
return HttpResponse("Created", headers=headers)
# if request.htmx: # could use this but the url doesn't update, stays as create, would need to use HX-Push header & HttpResponse + context somehow
# context = {
# "object": obj
# }
# return render(request, "recipes/partials/detail.html", context)
return redirect(obj.get_absolute_url())
return render(request, "recipes/create.html", context)
#login_required
def recipe_ingredient_update_hx_view(request, parent_id=None, id=None): # this is both create & edit, can create
if not request.htmx:
raise Http404
try:
parent_obj = Recipe.objects.get(id=parent_id, user=request.user)
except:
parent_obj = None
if parent_obj is None:
return HttpResponse("Not Found.")
instance = None
if id is not None:
try:
instance = RecipeIngredient.objects.get(recipe=parent_obj, id=id) # think of this as an object if that helps
except:
instance = None
form = RecipeIngredientForm(request.POST or None, instance=instance)
url = reverse("recipes:hx-ingredient-create", kwargs={"parent_id": parent_obj.id})
if instance:
url = instance.get_hx_edit_url()
context = {
"url": url,
"form": form,
"object": instance
}
if form.is_valid():
new_obj = form.save(commit=False)
if instance is None:
new_obj.recipe = parent_obj
new_obj.save()
context['object'] = new_obj # because it's possible the object/instance in None
return render(request, "recipes/partials/ingredient-inline.html", context)
return render(request, "recipes/partials/ingredient-form.html", context)
forms.py
from django import forms
from .models import Recipe, RecipeIngredient
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ['name', 'image', 'description', 'notes', 'cookTime']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
new_data = {
"placeholder": f"Recipe {str(field)}",
"class": "form-control",
}
self.fields[str(field)].widget.attrs.update(new_data)
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = ['name', 'quantity', 'unit']
urls.py
from django.urls import path
from .views import (
recipe_list_view,
recipe_delete_view,
recipe_create_view,
recipe_update_view,
recipe_detail_hx_view,
recipe_ingredient_update_hx_view,
recipe_ingredient_delete_view,
recipe_ingredient_image_upload_view,
recipe_ingredient_url_scrape_view
)
app_name='recipes' # allows use of recipes:list as a reverse url call
urlpatterns = [
path('', recipe_list_view, name='list'), # index / home / root
path('create/>', recipe_create_view, name='create'),
path('hx/<int:parent_id>/ingredient/<int:id>', recipe_ingredient_update_hx_view, name='hx-ingredient-detail'), #or detail
path('hx/<int:parent_id>/ingredient/', recipe_ingredient_update_hx_view, name='hx-ingredient-create'),
]
create.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div class="container-fluid px-5">
<h1 class="pb-5">Create Recipe</h1>
<div id="recipe-container">
<form action='.' method="POST" hx-post='.'>
{% csrf_token %}
<div class='row'>
<div class="row d-flex pb-5">
<div class="col-12 col-lg-6 justify-content-center d-flex order-first order-lg-last pictureBox"
style="height: 400px; width:450; border: solid tomato 1px;">
<div class="align-self-center">
{{ form.image|as_crispy_field }}
</div>
</div>
<div class="col-12 col-lg-6 order-lg-first">
<div class="pb-3">{{ form.name|as_crispy_field }}</div>
<div class="pb-3">{{ form.description|as_crispy_field }}</div>
</div>
</div>
<div class="col-12 col-md-6">
{{ form.notes|as_crispy_field }}
</div>
<div class="col-12 col-md-6">
{{ form.cookTime |as_crispy_field }}
</div>
</div>
</div>
<div class='col-12 col-md-4'>
<!-- ADD INGREDIENTS ? -->
</div>
<div class="htmx-indicator">Loading...</div>
<div class="d-flex">
<button class="btn btn-success htmx-inverted-indicator" style='margin-top:10px;' type='submit'>Save</button>
<a class="btn btn-danger" href='{% url "recipes:list" %}'>Delete</a>
</div>
{% if message %}
<p>{{ message }}</p>
{% endif %}
</form>
</div>
</div>
{% endblock content %}
ingredient-form.html
<form action='.' method="POST" hx-post='{% if url %} {{ url }} {% else %} . {{% endif %}' hx-swap='outerHTML'>
{% csrf_token %}
{{ form.as_p}}
<div class="htmx-indicator">Loading...</div>
<button class="htmx-inverted-indicator" style='margin-top:10px;' type='submit' >Save</button>
</form>
ingredient-inline.html
<div class="py-1" id="ingredient-{{object.id}}">
<p>{% if not edit %} <b>{{ object.quantity }} {% if object.unit %} {{ object.unit }} {% endif %}</b> {% else %} {{ object.quantity }} {{ object.unit }} {% endif %} - {{ object.name }}</p>
{% if edit %}
<button class="btn btn-primary" hx-trigger='click' hx-get='{{ object.get_hx_edit_url }}' hx-target="#ingredient-{{object.id}}">Edit</button> <!-- target will replace whole div-->
<button class="btn btn-danger" href='{{ object.get_delete_url }}' hx-post='{{ object.get_delete_url }}' hx-confirm="Are you sure you want to delete {{ object.name }}?" hx-trigger='click' hx-target="#ingredient-{{object.id}}" hx-swap="outerHTML">Delete</button>
{% endif %}
</div>
The key to this problem is using a formset, as you will likely want to save multiple ingredients to the recipe. Django documentation outlines how to use them. Your view would end up looking something like below, allowing you to save the parent model, which will give you the parent instance/primary key to then save the ingredients.
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
RecipeIngredientFormset = formset_factory(RecipeIngredientForm)
formset = RecipeIngredientFormset(request.POST or None)
context = {
"form": form,
"formset": formset,
}
if request.method == "POST":
if form.is_valid() and formset.is_valid():
parent = form.save(commit=False)
parent.user = request.user
parent.save()
#recipe ingredients
for form in formset:
child = form.save(commit=False)
child.recipe = parent
child.save()
I am trying to work on a django-based project/website that logs who took out our dogs last. I am very new to django so please bear with lack of knowledge in the field. I currently have my project set up so that I have models based on users/family members, the families' dogs, posts (logs of when and who took out the dogs), and actions, which is a child class that uses the dog and post models as foreign keys to see if the dogs peed and or pooped when they were taken out. The part that I am stuck on is trying to figure out how I create the post form. Since we have two dogs I need the form/post page to display a set of check boxes (for peeing and pooping actions) for each dog model that exists. While I can successfully display one action set, I run into the difficulty of trying to display the correct number of action sets and also to post the set of actions and the post itself. I have been trying to work with the formset_factory function but I am confused as to how I get it to function properly. Can anyone help? I have been stuck on this problem for quite a while and any support would be greatly appreciated.
models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Dog(models.Model):
name = models.CharField(max_length = 20)
class Post(models.Model):
walker = models.ForeignKey(User, on_delete = models.CASCADE)
time_posted = models.DateTimeField(default = timezone.now)
issues = models.TextField(max_length = 300)
class Action(models.Model):
post = models.ForeignKey(Post, on_delete = models.CASCADE)
dog = models.ForeignKey(Dog, on_delete = models.CASCADE)
peed = models.BooleanField(default = False)
pooped = models.BooleanField(default = False)
forms.py:
from django import forms
from django.forms import formset_factory
from dog_log_app.models import *
class Log_Form(forms.ModelForm):
class Meta:
model = Post
fields = ("issues",)
class Action_Form(forms.Form):
peed = forms.BooleanField(initial = False, required = False)
pooped = forms.BooleanField(initial = False, required = False)
views.py:
from django.shortcuts import render
from django.forms import formset_factory, modelformset_factory
from .models import Post, Dog, Action
from dog_log_app.forms import *
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect
def home(request):
context = {
'posts': Post.objects.all().order_by('-time_posted'),
'actions': Action.objects.all(),
'dogs': Dog.objects.all()
}
return render(request, 'dog_log_app/home.html', context)
def post(request):
form_post = Log_Form(request.POST or None)
form_actions = modelformset_factory(Action, fields = ('peed', 'pooped'), extra = Dog.objects.count())
if request.method == 'POST':
form_post = Log_Form(request.POST)
if form_post.is_valid() and form_actions.is_valid():
post_save = form_post.save(commit = False)
post_save.walker = request.user
post_save.save()
form_actions.save()
return HttpResponseRedirect('..')
context = {
'post': form_post,
'action': formset_factory(Action_Form, extra = Dog.objects.count()),
'dogs': Dog.objects.all()
}
return render(request, 'dog_log_app/post_form.html', context)
post_form.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Post</title>
</head>
<body>
<div>
<form method="POST">
{% csrf_token %}
<fieldset>
<legend>Create Post</legend>
<h3>{{dogs.name}}</h3>
<p>{{action.as_p}}</p>
<p>{{post.as_p}}</p>
</fieldset>
<div>
<button type="submit" value="Ok">Post</button>
</div>
</form>
</div>
</body>
</html>
what you can try would be to use ajax to add the amount you want, what I usually do is use the form:
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
class PostForm(ModelForm):
class Meta:
model = Post
fields = (
'issues',
)
class ActionForm(ModelForm):
class Meta:
model = Action
fields = (
'peed', 'pooped'
)
ActionFormSet = inlineformset_factory(
Post, Action, form=ActionForm,
fields = ['peed', 'pooped'], extra=1, can_delete=True,
)
With the forms created, we move on to the views, which will be class-based for convenience:
from django.db import transaction
from django.views.generic import CreateView, UpdateView
from .models import Post
from .forms import PostForm, ActionFormSet
class PostCreateView(CreateView):
template_name = 'your template'
form_class = PostForm
success_url = reverse_lazy('your url')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['formset'] = ActionFormSet(self.request.POST)
else:
data['formset'] = ActionFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
with transaction.atomic():
self.object = form.save()
if formset.is_valid():
formset.instance = self.object
formset.instance.walker = self.request.user
formset.save()
return super().form_valid(form)
class PostUpdateView(UpdateView):
template_name = 'your template'
form_class = PostForm
model = Post
success_url = reverse_lazy('your url')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['formset'] = ActionFormSet(
self.request.POST, instance=self.object)
else:
data['formset'] = ActionFormSet(instance=self.object)
return data
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
with transaction.atomic():
self.object = form.save()
if formset.is_valid():
formset.instance = self.object
formset.save()
return super().form_valid(form)
and to display the information you can use a jquery library for the dynamic information on saving:
enter link description here
With that the configuration is much easier
An example template would be:
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container">
<div class="card">
<div class="card-content">
<h2>Agregar Proyecto</h2>
<div class="col-md-4">
<form id="form-container" method="post">
{% csrf_token %}
{{ form }}
<h2>Actions</h2>
<table>
{{ formset.management_form }}
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr id="projects_data">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<button type="submit" class="btn btn-success">{{ message }}</button>
Cancel
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$(function () {
$('#projects_data').formset({
prefix: '{{ formset.prefix }}',
addText: 'create',
deleteText: 'remove',
});
})
</script>
{% endblock %}
and in that way add data dynamically.
I have a form which creates a new category. Previously, the form was in a different template which worked fine but since it's only a simple form I have decided to render it in modal form instead of redirecting to a different page.
The user can add a new category, however the success message and the page rendering after the form submit is not shown. It only shows up if you refresh the page. The response message is 302.
I've done similar method with other forms which worked perfectly fine.
forms.py
class CategoryModelForm(forms.ModelForm):
def clean_name(self):
print(self.cleaned_data['name'])
name = self.cleaned_data['name']
try:
Category.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('Category Name already exists.')
class Meta:
model = Category
fields = ['name']
views.py
#method_decorator(login_required, name='dispatch')
class CategoryView(TemplateView):
template_name = 'content/category_list.html'
def get_context_data(self, **kwargs):
context = super(CategoryView, self).get_context_data(**kwargs)
categories = Category.objects.all()
user = self.request.user
category_list = []
for category in categories:
article_count = category.article_count(user)
include = category.show or user.usertype_is_staff() or user.is_superuser
requested_by = category.requested_by if category.requested_by else ''
cat = {
'reference': category.pk,
'name': category.name,
'show': category.show,
'article_count': article_count,
'has_articles': article_count > 0,
'requested_by': requested_by,
'requested_by_name': requested_by.profile.full_name if requested_by and requested_by.profile.full_name
else '-'
}
include and category_list.append(cat)
context['categories'] = category_list
context['form'] = CategoryModelForm(self.request.POST or None)
return context
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if context['form'].is_valid():
context['form'].save()
messages.success(request, 'Successfully created new category.')
return redirect('content:category')
return super(CategoryView, self).render_to_response(context)
category_list.html
<div id="newCategory" data-id="new-account" class="modal fade bd-example-modal-lg"
tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="syn-breadcrumb">
<div class="syndicator-form-container">
<form class="syndicator-form" action="{% url 'content:category' %}"
method="post">
{% csrf_token %}
<div class="">
<h3 class="lighter-weight">
{% if user.usertype_is_supplier %}
Request New Category
{% else %}
Add New Category
{% endif %}
</h3>
</div>
<div class="form-fields">
<div class="non-field-errors">
{{ form.non_field_errors }}
</div>
<div id="{{ form.name.name }}" class="d-flex flex-column fields">
<div class="lighter-weight"><label for="id_name">Name</label></div>
<div>{{ form.name }}</div>
<div class="field-errors">{{ form.name.errors }}</div>
</div>
</div>
<div class="submit-button">
<button type="submit" class="btn btn-primary form-control">{% trans 'Submit' %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
I want the page to be redirected to an updated list of the categories after the form submit with the success message. As well as show the error message if the category name already exists or if the fields re empty.
You are using self.request instead of request even though request is already passed in post method
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['form'] = CategoryModelForm(request.POST or None) # use request.POST
I would rather suggest you to use a FormView or rather a generic view. (docs). You won't need to handle the form by yourself.
#method_decorator(login_required, name='dispatch')
class CategoryView(FormView):
template_name = 'content/category_list.html'
form = CategoryModelForm
success_url = reverse("content:category")
def form_valid(self, form):
self.obj = form.save(commit=True)
messages.success(self.request, 'Successfully created new category.')
return super(CategoryView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(CategoryView, self).get_context_data(**kwargs)
categories = Category.objects.all()
user = self.request.user
category_list = []
for category in categories:
article_count = category.article_count(user)
include = category.show or user.usertype_is_staff() or user.is_superuser
requested_by = category.requested_by if category.requested_by else ''
cat = {
'reference': category.pk,
'name': category.name,
'show': category.show,
'article_count': article_count,
'has_articles': article_count > 0,
'requested_by': requested_by,
'requested_by_name': requested_by.profile.full_name if requested_by and requested_by.profile.full_name
else '-'
}
include and category_list.append(cat)
context['categories'] = category_list
# form will be automatically added to context
views.py
from django.shortcuts import render, get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.views.decorators.http import condition
from django.views.generic.detail import SingleObjectMixin
from django.utils import timezone
from django.views.generic import \
ListView, DetailView
from .models import (
Book,
Category,
Author,
Language,
Currency,
Tag,
)
from django.db.models import Q
class BookList(ListView):
model = Book
context_object_name = 'book_list'
template_name = 'books/book_lists.html'
paginate_by = 12
extra_context = {
'category_list': Category.objects.all(),
'author_list': Author.objects.all(),
'language_list': Language.objects.all(),
}
def get_queryset(self):
query = self.request.GET.get('q')
if query:
object_list = self.model.objects.filter(
Q(name_of_the_book__icontains=query) |
Q(author__first_name__icontains=query) |
Q(category__name__icontains=query)
)
else:
object_list = self.model.objects.all()
return object_list
class SingleCategoryView(DetailView):
model = Category
template_name = 'books/single_category.html'
paginate_by = 12
extra_context = {
'category_list': Category.objects.all(),
'author_list': Author.objects.all(),
'language_list': Language.objects.all(),
}
class SingleAuthorView(DetailView):
model = Author
template_name = 'books/single_author.html'
extra_context = {
'category_list': Category.objects.all(),
'author_list': Author.objects.all(),
'language_list': Language.objects.all(),
}
class SingleLanguage(DetailView):
model = Language
template_name = 'books/single_language_list.html'
extra_context = {
'category_list': Category.objects.all(),
'author_list': Author.objects.all(),
'language_list': Language.objects.all(),
}
class BookDetails(DetailView):
model = Book
template_name = 'books/book_details.html'
# extra_context = {
# 'category_list': Category.objects.all(),
# 'author_list': Author.objects.all(),
# 'language_list': Language.objects.all(),
# }
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
Here BookList view is my home page. In my base template, I've added some kind of category such as SingleCategoryView, SingleLanguage view etc as dynamic URL dropdown in the navbar.
base.html
<!-- nav bar -->
<div class="navbar">
Home
<div class="dropdown">
<button class="dropbtn">Category
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-content">
{% for object in category_list %}
{{ object.name }}
{% endfor %}
</div>
</div>
<div class="dropdown">
<button class="dropbtn">Author
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-content">
{% for object in author_list %}
{{ object.first_name }} {{ object.last_name }}
{% endfor %}
</div>
</div>
<div class="dropdown">
<button class="dropbtn">Language
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-content">
{% for object in language_list %}
{{ object.language }}
{% endfor %}
</div>
</div>
</div>
<!-- nav bar end -->
whilst on the homepage or any other category list page that categories dropdown navbar are showing good but when I'm going to in my BookDetail page it doesn't show. I commented out that codes into BookDetail View.
Please visit this link, you will understand clearly. https://clean-book-library.herokuapp.com/
Keep your cursor author, language or category. You will see the list and then go to the details page of any book and keep your cursor again you won't see that.
The main question, why doesn't work BookDetails View code that I've commented out and how to create base search bar into one class views such like as dropdown navbar.
Thanks.
You can pass extra context data in get_context_data function.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
context.update(self.extra_context)
return context