How to do a POST request in a DetailView - django

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)

Related

Foreign key is not assigned to a model in database

I recently started learning django and was making a CRM.
models.py:
class Complaint(models.Model):
SOURCE_CHOICES=(
('email','E-mail'),
('call','Call'),
('ticket','Ticket')
)
store_name=models.CharField(max_length=20)
store_contact_no=models.IntegerField(max_length=10)
store_id=models.CharField(max_length=7)
source=models.CharField(choices=SOURCE_CHOICES, max_length=10)
agent=models.ForeignKey("Agent", null = True, blank = True, on_delete=models.SET_NULL)
category=models.ForeignKey("Category", related_name="complaints", null = True, blank = True, on_delete=models.SET_NULL)
description = models.TextField()
customer = models.ForeignKey("Customer", null = True, blank = True, on_delete=models.SET_NULL)
def __str__(self):
return f"{self.store_name} {self.store_id}"
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
forms.py
class CustomerComplaintForm(forms.ModelForm):
class Meta:
model = Complaint
fields = (
'store_name',
'store_id',
'store_contact_no',
'description',
)
views.py
class
CustomerComplaintCreateView(OwnerCustomerAndLoginRequiredMixin,
generic.CreateView):
template_name = "customercomplaint_create.html"
form_class = CustomerComplaintForm
def get_success_url(self):
return "/complaints"
def form_valid(self, form):
complaint = form.save(commit=False)
complaint.Customer = self.request.user.username
complaint.source = 'ticket'
complaint.save()
return super(CustomerComplaintCreateView,
self).form_valid(form)
html template:
{% extends "base.html" %}
{% load tailwind_filters %}
{% block content %}
<div class="max-w-lg mx-auto">
<a class="hover:text-blue-500" href="/complaints">Go back to complaints </a>
<div class="py-5 border-t border-gray-200">
<h1 class="text-4xl text-gray-800"> Create a new complaint </h1>
</div>
<form method='post' class="mt-5">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="w-full bg-blue-500 text-white hover:bg-blue-600 px-
3 py-2 rounded-md">Create</button>
</form>
</div>
{% endblock content %}
mixins.py
class OwnerCustomerAndLoginRequiredMixin(AccessMixin):
"""Verify that the current user is authenticated and is an owner or customer"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.is_owner and not request.user.is_customer:
return redirect("/complaints")
return super().dispatch(request, *args, **kwargs)
The problem here is that, the source field gets filled in the database with Ticket as intended. But the 'Customer' field is not populated with the username. 'Self.request.user.username' is not the problem here as the username is being printed correctly in the console.
The issue is complaint.Customer = self.request.user.username, you're trying to assign a username to a supposedly Customer object. Here's an approach you could take to solve the issue though.
Within the views.py file, the view class.
You could get the customer object and then assign it to the customer field on the complaint object.
from django.shortcuts import get_object_or_404
def form_valid(self, form):
complaint = form.save(commit=False)
customer = get_object_or_404(Customer, user=self.request.user) # recommended
# or
# customer = get_object_or_404(Customer, user__username__iexact=self.request.user.username)
if customer:
# Here on your model you have a lowercase `c` for the customer field, not 'C`
complaint.customer = customer # -> This will assign the customer object, "FK".
complaint.source = 'ticket'
complaint.save()
return super(CustomerComplaintCreateView, self).form_valid(form)
That should work.
this must be the User not the user name
Because Cutomer is User object not only the Uesrname
def form_valid(self, form):
complaint = form.save(commit=False)
complaint.Customer = self.request.user
complaint.source = 'ticket'
complaint.save()

Django - saving model via a form is not working

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

django class based view pass url parameter in create post to current category

lets's say that i have three categories (tutorials, news, jobs).
and i have class based views to list all posts, list posts by category and create new posts.
and sure post is the same model and fields to all categories.
my problem is :
if user was in category list template (let's say tutorial) .. i want the user when he create new post .. it is saved directly to tutorial category .. and if user was in list template (let's say news) .. he will create new post which will be saved directly to news category.
i mean create new post saved directly to current category.
i believe i will use (pass url parameter to class based views) but actually i failed to do that .. and i searched tonnage of questions without got what i want.
can any body help .. with sample please.
models.py
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50, unique=True)
def save(self, *args, **kwargs):
if not self.slug and self.name:
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
def PostListView(request, category_slug=None):
category = None
posts = Post.objects.all().prefetch_related().annotate(commentscountperpost=Count('comments'))
categories = Category.objects.prefetch_related().annotate(total_product_category=Count('post'))
if category_slug:
category = Category.objects.get(slug=category_slug)
posts = posts.filter(category=category)
context = {
'title': 'Home Page',
'posts': posts,
'total_posts': total_posts,
'categories': categories,
'category': category,}
return render(request, 'blog/index.html', context)
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = PostCreateForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
forms.py
class PostCreateForm(forms.ModelForm):
title = forms.CharField(label='Title')
content = forms.CharField(label='Content', widget=forms.Textarea)
class Meta:
model = Post
fields = ['title', 'content']
urls.py
path('index_list/', PostListView, name='list'),
path('<slug:category_slug>', PostListView, name='post_category_list'),
path('new_post/', PostCreateView.as_view(), name='new_post'),
new_post.html template
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<div class="border p-4 mb-5">
<legend class="border-bottom pb-1 mb-3">New Post </legend>
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<input class="btn btn-secondary mt-4" type="submit" value="Add New Post">
</form>
</div>
{% endblock content %}
list.html template
{% for category in categories %}
<h5><a class="text-primary" href="{% url 'post_category_list' category.slug %}">
{{ category.name }} ({{ category.total_product_category }})</a></h5>
{% endfor %}
<h5>{{ total_posts }} Total Posts </h5>
{% if category %}
New {{ category }}
{% endif %}
You can try like this:
class PostCreateView(generic.CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = CreatePostForm
slug_url_kwarg = 'slug'
def form_valid(self, form):
category = Category.objects.get(slug=self.kwargs['slug'])
form.instance.category = category
form.instance.author = self.request.user
return super(PostCreateView, self).form_valid(form)
And in the urls
path('new_post/<slug>/', PostCreateView.as_view(), name='new_post'),
yes i found it , depending on arjun answer, greate thanks for arjun
in list posts per category template change :
New {{ category }}
to:
New {{ category }}
and it works fine,
thanks.

My Class Based Views are not adding slug as well as tags in the form-valid(self,form) of the Class in Django

# Post Model for addding the Post object to the DataBase
class Post(models.Model):
STATUS_CHOICE = [
('draft', "Draft"),
('published', "Published")
]
title = models.CharField(max_length=300)
slug = models.SlugField(max_length=250, unique_for_date="publish", null=True, blank=True)
author = models.ForeignKey(User, related_name="blog_posts", on_delete=models.CASCADE)
body = RichTextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICE, default="draft")
image_upload = models.ImageField(blank=True, null=True, upload_to="post_pics")
# For Tagging Features
tags = TaggableManager(blank=True)
class Meta:
ordering = ('-publish',)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post_detail',
args=[self.publish.strftime('%Y'),
self.publish.strftime('%m'),
self.publish.strftime('%d'),
self.slug])
# url for adding the creating and saving the post object to the Database
url(r'^create_post/', views.PostCreateView.as_view(), name="create_post")
# Class-Based View for adding the Post
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'body', 'status', 'tags', 'image_upload']
def get_success_url(self, **kwargs):
return self.object.get_absolute_url()
def form_valid(self, form):
obj = form.save(commit=False)
obj.author = self.request.user
obj.slug = slugify(obj.title)
obj = form.save()
obj.tags.add(slugify(obj.title))
obj.save()
return super(PostCreateView, self).form_valid(form)
# form for adding the fields to the Post Model
<!DOCTYPE html>
<html lang="en">
{% load crispy_forms_tags %}
<head>
<meta charset="UTF-8">
<title>Create Posts</title>
</head>
<body class="container" style="text-align:center;border:1px solid red;padding:10px;">
{% include './post/navbar.html' %}
<div class="alert alert-info" style="text-align:center;display:inline-block">
Create Post
</div>
<div>
<form method="POST" enctype="multipart/form-data" style="width:80%;margin:0px auto">
{% csrf_token %}
{{form.media}}
{{form|crispy}}
<input type="hidden" name="next" value="{{next}}">
<input type="submit" name="Register" class="btn btn-success" value="POST">
</form>
</div>
</body>
</html>
Here the slug added is not saving to the database instead the actual title which user enters in the form is getting saved in the database . Also, I am adding the tags if user fails to enter the tags. In that case, I am entering the tags on the basis of the title by slugification of the title, but the tags are getting added into the Taggit Model but not getting entered in the tags field of the Post Model.
try to add this in your Post class.
def save(self, *args, **kwargs):
"""
form post - auto save slug
"""
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)

DeleteView not deleting

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)