Associating a model with another model when saving it - django

I have the following code. The product is like this: There's a book list. On each book item is a form. This form saves a post and makes it related to this particular book. When you click a book item, you see a list of posts, all related to this book item. So far the book list works fine, saving post data works fine, and when you click a list, it (the DetailView) shows the list of posts (instead of book description, which is usually how DetailView is used) fine. What's not working is saving the post to be related to the particular book on whom is the form that saves the post. More simply, when you save a post, the post should be related to the book that the form is located in.
I'd really appreciate your help. Thanks!
views.py:
class BookList(ListView):
model = Book
template_name='books/books.html'
class PostForm(ModelForm):
class Meta:
model = Post
# Previously I tried, unsuccessfully, this among others:
# books = Book.objects.filter(book)
# posts = Post.objects.filter(the_book=books)
# model = posts
fields = ['post']
widgets = {
'post': forms.Textarea()
}
def get_context_data(self, **kwargs):
context = super(BookList, self).get_context_data(**kwargs)
context['form'] = BookList.PostForm
return context
def post(self, request, *args, **kwargs):
form = BookList.PostForm(request.POST)
if form.is_valid():
form.save()
return render(request, self.template_name, {'form': form })
class Posts(DetailView):
model = Book
template_name='books/post_create.html'
slug_field = 'id'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
book = self.object
posts = Post.objects.filter(the_book=book)
context['posts'] = posts
return context
models.py:
class Book(models.Model):
book = models.CharField(max_length=1000, blank=False, null=False, default="1")
def __str__(self):
return self.book
class Post(models.Model):
post = models.CharField(max_length=1000, blank=False, null=False)
the_book = models.ForeignKey('Book', on_delete=models.CASCADE, default="1")
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.post
books.html:
<ul>
{% for book in object_list %}
<div class='ui card'>
<a class="content" href='{% url 'books:posts' book.id %}'>
<div class="header">{{ book }}</div>
</a>
<div class="ui bottom attached button">
<form class='ui form' action='' method='post'> {% csrf_token %}
{{ form.as_p }}
<div class='postbutton'>
<input class='ui button' type="submit" value="Done" />
</div>
</form>
</div>
</div>
{% empty %}
<h5>You don't have any books!</h5>
{% endfor %}
post_create.html (the name will be changed later...):
<ul>
{% for post in posts %}
<div class='ui card'>
<a class="content">
<div class="header">{{ post }}</div>
</a>
</div>
{% empty %}
<h5>You don't have any posts!</h5>
{% endfor %}

Try this: add a book_id hidden input in your form:
<form class='ui form' action='' method='post'> {% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="book_id" value="{{ book.id }}">
<div class='postbutton'>
<input class='ui button' type="submit" value="Done" />
</div>
and use it in your BookList view:
def post(self, request, *args, **kwargs):
form = BookList.PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.the_book_id = request.POST['book_id']
post.save()
return render(request, self.template_name, {'form': form })

Related

Why is the Django form not rendering in a class-based view?

Background: I've been working on the 'mini blog' challenge in Mozilla's tutorial. Got 'author' (user), 'post' working. Added 'comment'. With bits of code from this (among others), I managed to use a function-based view to get 'CommentForm' work on the 'post-detail' page. But trying to convert the function-based view to a class-based view stumped me. Because the various errors I encountered all relate to some key fundamental concepts, like explicit/implicit 'request', passing 'context', I've been working at it, hoping to understand Django better. But I'm getting nowhere. The problem is the form, 'CommentForm', doesn't render in the template. At one point, it rendered, but 'submit' the form led to not allowed error. I figured it related to 'GET' vs 'POST'. At this point, I'm not sure where the issue is--views? template? Appreciate some pointers.
Here's my code:
models.py
class Post(models.Model):
post = models.TextField(max_length=1000)
post_title = models.CharField(max_length=100)
description = models.TextField(max_length=500)
post_created_at = models.DateTimeField(auto_now_add=True)
post_updated_at = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name="posts", on_delete=models.CASCADE)
#property
def num_comments(self):
return Comment.objects.filter(post_connected=self).count()
def __str__(self):
return f'{self.author or ""} – {self.post_title[:40]}'
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
def get_absolute_url(self):
return "/blog/{}/".format(self.pk)
class Meta:
ordering = ['-post_created_at']
class Comment(models.Model):
comment_title = models.CharField(max_length=100)
comment = models.TextField(max_length=1000)
comment_created_at = models.DateTimeField(auto_now_add=True)
comment_updated_at = models.DateTimeField(auto_now=True)
commenter = models.ForeignKey(User, related_name="commented", null=True, blank=True, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
post_connected = models.ForeignKey(Post, related_name='comment', on_delete=models.CASCADE, default=None, null=True) #
class Meta:
ordering = ['comment_created_at']
def __str__(self):
return str(self.commenter) + ' : ' + self.comment_title[:40]
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
### function-based views.py (clumsy, but the form renders and saves)
def post_detail(request, pk):
template_name = 'post_detail.html'
post = get_object_or_404(Post, pk=pk)
comments = post.comment.filter(active=True)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post_connected = post
new_comment.save()
return redirect('/')
else:
comment_form = CommentForm()
return render(request, 'post_detail.html', {'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
### function-based template (everything works):
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<!-- comments -->
<h2>{{post.num_comments}} comments:</h2>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.comment_title }} - {{comment.commenter}}
<span class=" text-muted font-weight-normal">
{{ comment.comment_created_at }}
</span>
</p>
{{ comment.comment | linebreaks }}
</div>
{% endfor %}
</div>
</div>
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<h3>Leave a comment</h3>
<form method="post" style="margin-top: 1.3em;">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
</div>
</div>
urls.py (for both views, I comment out the url (and view) not in use)
urlpatterns += [
path('blog/<int:pk>/', views.post_detail, name='post_detail'),
#path('blog/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'),
]
Same models.py, but class-based view:
### Class-based view (form doesn't get rendered at all)
class PostDetailView(generic.DetailView):
# pass
model = Post
form_class = CommentForm
def post_detail_view(self, request, primary_key):
post = get_object_or_404(Post, pk=primary_key)
post_connected = Comment.objects.filter(
post_connected=self.get_object()).order_by('-comment_created_at')
comments = post_connected
return comments
def get_success_url(self):
return reverse('post-detail', kwargs={'pk' : self.object.pk})
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(PostDetailView, self).get_form_kwargs(
*args, **kwargs)
return kwargs
if self.request.user.is_authenticated:
comment_form = CommentForm()
comment_form = CommentForm(instance=selfrequest.user)
new_comment = CommentForm(request.POST)
new_comment.save()
def get_object(self):
post = super().get_object()
post_connected = Comment.objects.filter(
post_connected=self.get_object()).order_by('-comment_created_at')
if self.request.user.is_authenticated:
self.request.user = CommentForm(instance=self.request.user)
comment = CommentForm(request.POST)
comment.save()
post.author = User.objects.filter(id=post.author.id)
post.views +=1
post.save()
return post
The code for the templates is identical for both views, but the files are in different directories:
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<!-- comments -->
<h2>{{post.num_comments}} comments:</h2>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.comment_title }} - {{comment.commenter}}
<span class=" text-muted font-weight-normal">
{{ comment.comment_created_at }}
</span>
</p>
{{ comment.comment | linebreaks }}
</div>
{% endfor %}
</div>
</div>
<div class="col-md-8 card mb-4 mt-3 ">
<div class="card-body">
<h3>Leave a comment</h3>
<form action="{% url 'post-detail' post.id %}" method="POST" style="margin-top: 1.3em;">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
</div>
</div>

Django pass PK from post to next page

I currently have a blog post feature on my site. I want users to be able to create sub posts
(build log ) off of the main posts.
I created this model with a FK to the Post model
class BuildLog(models.Model):
title = models.CharField(max_length=100)
content = RichTextUploadingField(blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
I then added a button on the post page and created a form. Everything is working as expected but when I submit the form I get the error
null value in column "post_id" of relation "blog_buildlog" violates not-null constraint
If I understand the error correctly it is saying I am not passing the PK of the post into the form.
Views
def DetailPostView(request, pk):
model = Post
post = Post.objects.get(pk=pk)
form = CommentForm
comments = Comment.objects.filter(post=post)#.order_by('-create')
if request.method == 'POST':
# A comment was posted
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.author = request.user
new_comment.post = post
new_comment.save()
context = {
'post':post, 'form':form, 'comments':comments
}
return render(request, 'blog/post_detail.html', context)
class BuildLogView(LoginRequiredMixin, CreateView):
model = BuildLog
form_class = BuildLogForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def save(self, *args, **kwargs):
super(BuildLog,self).save(*args, **kwargs)
Url
path('build-log-form/', BuildLogView.as_view(), name='build-log-form'),
path('post/<int:pk>/', views.DetailPostView, name='post-detail')
How do I go about passing the post PK into the next view?
forms.py
class BuildLogForm(ModelForm):
class Meta:
model = BuildLog
fields = ['title' , 'content']
html
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="post" enctype="multipart/form-data" id="PostForm" novalidate>
<!--id="modelForm"-->
{% csrf_token %}
<fieldset class="django-ckeditor-widget">
<legend class="border-bottom mb-4">New Build Log</legend>
{{ form.title | as_crispy_field }}
{{ form.media }}
{{ form.content | as_crispy_field | safe}}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
</div>
{% endblock content %}
Just incase their is any confusing It goes Post --> BuildLog(subpost)

Can you help me fix a 405 error with Django form submission?

I'm having an issue when submitting a form within a class-based view. I'm using the FormMixin in a detail view, and when I submit the form I get a 405 error. I've tried chopping and changing the code in views, but nothing seems to be working.
Models
class Bid(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
tender = models.ForeignKey(Tender, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=100)
specification = models.TextField()
timeline = models.CharField(max_length=100)
date_posted = models.DateTimeField(default=timezone.now)
price = models.IntegerField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('bid-detail', kwargs={'pk': self.pk})
Views
class TenderDetailView(FormMixin, LoginRequiredMixin, DetailView):
model = Tender
template_name = 'it_me/tender_detail.html'
form_class = BidForm
def get_success_url(self):
return reverse('tender-detail', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super(TenderDetailView, self).get_context_data(**kwargs)
context['form'] = BidForm(initial={'post': 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.save()
return super(TenderDetailView, self).form_valid(form)
forms
class BidForm(forms.ModelForm):
class Meta:
model = Bid
fields = ('title', 'specification', 'timeline', 'price')
Templates
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit"> Post Bid </button>
</div>
</form>
</div>
{% for bid in tender.bids.all %}
<div class=" col-md-12 comment">
<div class="date">{{ bid.date_posted|date:"F d, Y" }}</div>
<strong>{{ bid.author }}</strong>
</div>
{% empty %}
<p>No Bids Yet </p>
{% endfor %}
urls
path('tender/<int:pk>/', TenderDetailView.as_view(), name='tender-detail')
Would really appreciate any help cause I am stumped.
DetailView has no support for post requests. It is aimed to show instance details. As you can see in the docs, the view supports only get method. To update your instance, you can use UpdateView. UpdateView is the generic view to handle update operation. You can also use these documentation pages for basic practices such as showing the instance details in DetailView.

How input date in my template ListView (request.POST.get) in Django

I have a class jourListView(ListView). I want to input date in my html template and then retrieve the date to make a filter.
My class jourListView(ListView) refers to two linked tables.
I am stuck at level. How to make my code functional?
here are my models, views and template.
class jour(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE, related_name='jour')
date = models.DateField(blank=True, null=True)
commentaire = models.CharField(max_length=500, blank=True, null=True)
class Meta:
managed = False
db_table = 'jour'
unique_together = (('id'),)
verbose_name = 'JOUR'
verbose_name_plural = 'JOUR'
id = models.AutoField(primary_key=True)
def get_absolute_url(self):
return reverse("jour_detail",kwargs={'pk':self.pk})
def __str__(self):
return str(self.date) ##
class activite(models.Model):
jour = models.ForeignKey('blog.jour',on_delete=models.CASCADE, related_name='jour' )
name= models.CharField(max_length=500, blank=True, null=True)
class Meta:
managed = False
db_table = 'activite'
unique_together = (('id'),)
verbose_name = 'ACTIVITÉ'
verbose_name_plural = 'ACTIVITÉ'
id = models.AutoField(primary_key=True)
def __str__(self):
return self.type
def get_absolute_url(self):
return reverse("jour_list")
My view:
class jourListView(ListView):
model = jour
def get_context_data(self, **kwargs):
if request.method == 'POST':
date_insert = request.POST.get('date_ref')
context = super(jourListView, self).get_context_data(**kwargs)
context['count'] = self.get_queryset().count()
return context
def get_queryset(self):
return jour.objects.filter(date__lte=timezone.now()).filter(user=self.request.user).filter(date__in=date_insert)
My html template:
{% if user.is_authenticated %}
<div class="container text-center">
<form class="form-signin" id="login_form" method="post" action="/blog/list/">
{% csrf_token %}
<br>
<input type="date" name="date_ref" class="form-control" placeholder="SAISIE DATE " value="" required autofocus>
<br>
<button class="btn btn-lg btn-primary btn-block" type="submit">OK</button>
</form>
</div>
<div class="centerstage">
{% for jour in jour_list %}
<div class="post">
<p class='postcontent' ><strong>Date:</strong> {{ jour.date }}</p>
<p class='postcontent' ><strong>Commentaire:</strong> {{ jour.commentaire }}</p>
<a class="btn btn-primary" href="{% url 'jour_edit' pk=jour.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
<h1>Détails activités</h1>
</div>
-----------------
{% endfor %}
</div>
{% endif %}
I changed my view but it does not work.
class jourListView(ListView):
model = jour
template_name = "blog/list.html"
def get_context_data(self, **kwargs):
if request.method == 'POST':
date_insert = request.POST.get('date_ref')
context = super(jourListView, self).get_context_data(**kwargs)
context['count'] = self.get_queryset().count()
return context
def get_queryset(self):
queryset = jour.objects.filter(date__lte=timezone.now()).filter(user=self.request.user)
date_insert = request.POST.get('date_ref')
if date_insert:
queryset = queryset.filter(date=date_insert)
return queryset
In addition I have another error:
NameError at /blog/list/
name 'request' is not defined
Request Method:
GET
Request URL:
http://127.0.0.1:8000/blog/list/
Django Version:
2.0.2
Exception Type:
NameError
Exception Value:
name 'request' is not defined
You need to define date_insert inside the get_queryset method before you use it. For example:
class jourListView(ListView):
model = jour
def get_context_data(self, **kwargs):
if self.request.method == 'POST':
date_insert = self.request.POST.get('date_ref')
context = super(jourListView, self).get_context_data(**kwargs)
context['count'] = self.get_queryset().count()
return context
def get_queryset(self):
queryset = jour.objects.filter(date__lte=timezone.now()).filter(user=self.request.user)
date_insert = self.request.POST.get('date_ref')
if date_insert:
queryset = queryset.filter(date=date_insert)
return queryset
Once that's working, you may want to consider some further improvements
Renaming the model to Jour to match the Django style
Make sure you return a context in get_context_data for GET requests as well as POST requests.
Using LoginRequiredMixin to make sure that only logged in users can access the view
Adding checks to handle the case where date_insert isn't a valid date string.
I found the solution to my problem.
Here is the result:
I was inspired by the example in this link:
Django: Search form in Class Based ListView
My view:
class jourListView(ListView):
model = jour
template_name = "blog/list.html"
def get_context_data(self, **kwargs):
context = super(jourListView, self).get_context_data(**kwargs)
# FILTER BY CURRENT MONTH, USER
filter_ = jour.objects.filter(date__lte=timezone.now()).filter(user_id=self.request.user).order_by('-date')
if self.request.GET.get('date_ref'):
date_insert = self.request.GET.get('date_ref')
filter_ = filter_.filter(date=date_insert)
context['jourListView'] = filter_
return context
My template (blog/list.html):
{% extends 'blog/base.html' %}
{% block content %}
{% if user.is_authenticated %}
<form class="navbar-form navbar-right" action="." method="get">
{{ form.as_p }}
<input id="date_ref" name="date_ref" type="date" placeholder="Localizar..." class="form-control">
<button type="submit" class="btn btn-success form-control"><span class="glyphicon glyphicon-search"></span></button>
</form>
{% if jourListView %}
{% for select_value in jourListView %}
<div class="post">
<p class='postcontent' ><strong>Date:</strong> {{ select_value.date}}</p>
<p class='postcontent' ><strong>User ID:</strong> {{ select_value.user_id}}</p>
<p class='postcontent' ><strong>Status:</strong> {{ select_value.status}}</p>
</div>
-----------------
{% endfor %}
{% endif %}
{% endif %}
{% endblock %}

How to send some arguments along with CreateView and then access them in the template?

I was making a online store kind of website and am not able to make my add to cart option to work properly. I haven't yet linked the rest of the code to the button and am using an another link to operate it currently as you can see in the code.I want the form to submit the item name and brand automatically. Please suggest some way.
urls.py
url(r'^(?P<pk>[0-9]+)/addtocart/$', views.ItemAdd.as_view(), name='addtocart'),
models.py
class Mycart(models.Model):
name = models.CharField(max_length=250)
brand = models.CharField(max_length=250)
quantity = models.IntegerField(default='1')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('products:detail', kwargs={'pk': self.pk})
views.py
class ItemAdd(CreateView):
model = Mycart
fields = ['name', 'brand', 'quantity']
template_name = 'products/add_to_cart.html'
def get_context_data(self, **kwargs):
context = super(ItemAdd, self).get_context_data(**kwargs)
return context
add_to_cart.html
{% extends 'products/base.html' %} {% block body %}
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="name" value="{{ object.name }}">
<input type="hidden" name="brand" value="{{ object.brand }}">
<br>
<p>Enter Quantity</p>
<input type="number" name="quantity" value="">
<button type="submit" class="btn btn-success">Submit</button>
</form>
{% endblock %}
I understand that, when user click on item (product), you want automatically add name and brand to form, so user only need to enter quantity and submit form? Maybe you can try like this:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
product_pk = self.kwargs['pk']
product = Product.objects.get(pk=product_pk)
context.update({
'product': product
})
return context
Now you can access product in your template and get name and brand:
{{ product.name }}
{{ product.brand }}
You could use a Formview. Then you will have:
models.py
class Cart(models.Model):
quantity = models.PositiveIntegerField()
product = models.ForeignKey('products.Product')
forms.py
class AddCartForm(forms.ModelForm):
def save(self, product):
instance = super(AddCartForm, self).save(commit=False)
instance.product = product
instance.save()
return instance
class Meta:
model = Cart
fields = '__all__'
views.py
class AddCartView(FormView):
form_class = AddCartForm
success_url = '/'
def dispatch(self, request, *args, **kwargs):
product_pk = kwargs.get('product_pk')
self.product = get_object_or_404(Product, pk=product_pk)
return super(
AddCartView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kw):
context = super(AddCartView, self).get_context_data(**kw)
context.update(product=self.product)
return context
def form_valid(self, form):
form.save(product=self.product)
return super(AddCartView, self).form_valid(form)
add_cart.html
{% extends 'products/base.html' %} {% block body %}
<form action="{% url 'cart:add' product.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit" class="btn btn-success">Submit</button>
</form>
{% endblock %}