I started learning Django about a month ago and just finished Django for Beginners by William Vincent. The book ends with Ch.15: Comments and shows how the admin can add comments to posts.
Q: Can someone, please, show me or point me in the right direction as to how I can let registered users also add comments to posts? Is there perhaps a 3rd party app for that?
What I have so far:
Models:
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to='images/', null=True, blank=True, height_field=None, width_field=None)
upload = models.FileField(upload_to='files/', null=True, blank=True)
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comment')
comment = models.CharField(max_length=140)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE,
)
def __str__(self):
return self.comment
def get_absolute_url(self):
return reverse('article_list')
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'body', 'image', 'upload')
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('comment', 'author')
Views:
class ArticleListView(LoginRequiredMixin, ListView):
model = Article
template_name = 'article_list.html'
comment_form = CommentForm
login_url = 'login'
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Article
template_name = 'article_detail.html'
login_url = 'login'
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ('title', 'body', 'image', 'upload')
template_name = 'article_edit.html'
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.author != self.request.user:
raise PermissionDenied
return super().dispatch (request, *args, **kwargs)
class ArticleDeleteView(LoginRequiredMixin, DeleteView):
model = Article
template_name = 'article_delete.html'
success_url = reverse_lazy('article_list')
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.author != self.request.user:
raise PermissionDenied
return super().dispatch (request, *args, **kwargs)
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
form_class = PostForm
template_name = 'article_new.html'
login_url = 'login'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
URLs:
urlpatterns = [
path('<int:pk>/edit/', ArticleUpdateView.as_view(), name='article_edit'),
path('<int:pk>/', ArticleDetailView.as_view(), name='article_detail'),
path('<int:pk>/delete/', ArticleDeleteView.as_view(), name='article_delete'),
path('', ArticleListView.as_view(), name='article_list'),
path('new/', ArticleCreateView.as_view(), name='article_new'),]
Thank you for your attention.
Solved. In my views.py I added the following function:
def add_comment(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.article = article
comment.save()
return redirect('article_detail', pk=article.pk)
else:
form = CommentForm()
return render(request, 'add_comment.html', {'form': form})
Then the following .html file was added to templates:
add_comment.html
{% extends 'base.html' %}
{% block content %}
<h4>Add a Comment</h4>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.as_p }}</p>
<button type="submit" class="btn btn-success">Submit</button>
</form>
{% endblock content %}
P.S.: Initially I was getting an ImportError: cannot import name 'add_comment' from 'articles.views'.
I thought it was a circular import problem and what worked for me was just getting the def add_comment indentation right.
Related
can you help me?
I can't fix problem: my don't show error validation
when I write not unique slug at form -> no error at form
I think problem at use def post() or return redirect after validations form.
I try many different solutions but nothing helps.
Maybe you should use a non-standard way to report an error?
models.py
class ShortUrl(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Автор URL', null=True)
url = models.CharField('Ссылка', max_length=200)
slug = models.SlugField('Короткое имя ссылки', unique=True, max_length=20)
def __str__(self):
#return self.slug
return f"Короткая ссылка: {self.user} >> {self.slug}"
class Meta:
verbose_name = 'Ссылка'
verbose_name_plural = 'Ссылки
forms.py
class ShortURLForm(forms.ModelForm):
slug = forms.SlugField(
label='Название URL',
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Укажите уникальный URL'})
)
url = forms.CharField(
label='Ссылка',
required=True,
widget=forms.TextInput(attrs={'placeholder': 'Ссылка которую нужно сократить'})
)
class Meta:
model = ShortUrl
fields = ['user', 'url', 'slug']
widgets = {'user': forms.HiddenInput()}
views.py
class ShortURLPage(LoginRequiredMixin, ListView):
model = ShortUrl
template_name = 'main/shorts.html'
context_object_name = 'shorts'
def get_context_data(self, *, object_list=None, **kwargs):
ctx = super(ShortURLPage, self).get_context_data(**kwargs)
ctx['form'] = ShortURLForm()
userurls = ShortUrl.objects.filter(user=self.request.user)
ctx['shorts'] = userurls
ctx['title'] = 'Добавление ссылок'
return ctx
def post(self, request, *args, **kwargs):
post = request.POST.copy()
post['user'] = request.user
request.POST = post
form = ShortURLForm(request.POST)
if form.is_valid():
slug = form.cleaned_data['slug']
url = form.cleaned_data['url']
form.save()
return redirect('shorts')
shorts.html
<form method="post" class="form">
{% csrf_token %}
{{ form }}
<button class="button" type="submit">Создать ссылку</button>
</form>
urls.py
urlpatterns = [
path('', views.homepage, name='home'),
path('about/', views.about, name='about'),
path('shorts/', views.ShortURLPage.as_view(), name='shorts'),
path('shorts/<str:slug>/', views.urlRedirect, name='redirect'),
]
Ok, you're not so far away with accomplishing what you want.
Generally your post method should look like this:
def post(self, request, *args, **kwargs):
post = request.POST.copy()
post['user'] = request.user
request.POST = post
form = ShortURLForm(request.POST)
if form.is_valid():
slug = form.cleaned_data['slug']
url = form.cleaned_data['url']
form.save()
else:
context = {
'form': form,
}
return render(
request,
self.template_name,
context,
)
return redirect('shorts')
Then, you should write your shorts.html template like this:
<form method="post" class="form">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.errors }}
{{ form }}
<button class="button" type="submit">Создать ссылку</button>
</form>
models.py
class Contents(models.Model):
name = models.CharField(max_length=100)
formula = models.CharField(max_length=20)
description = models.TextField(null=True, max_length=100)
wiki = models.CharField(null=True, max_length=100)
youtube = models.CharField(null=True, max_length=100)
file = models.FileField()
def __str__(self):
return self.name
view.py
class ContentsListCreateAPIView(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'admin3.html'
style = {'template_pack': 'rest_framework/vertical/'}
def get(self, request):
queryset = Contents.objects.all()
serializer = ContentsSerializer
return Response({'serializer': serializer, 'contents': queryset, 'style': self.style})
def post(self, request):
queryset = Contents.objects.all()
serializer = ContentsSerializer(data=request.data)
print(serializer.is_valid())
print(serializer.data)
if serializer.is_valid():
serializer.save()
return redirect('contents_list')
return redirect('contents_list')
serializers.py
from rest_framework import serializers
from .models import Contents
class ContentsSerializer(serializers.ModelSerializer):
class Meta:
model = Contents
fields = '__all__'
admin.html
<form action="{% url 'contents_list' %}" method="POST">
{% csrf_token %}
{% render_form serializer %}
<input type="submit" value="Save">
It seems that the filefield does not get the proper input. I don't get what the problem is. The CreateAPIView works well when I use the default DRF template.
I am trying to save forms via Post by TemplateView, but when def Post calls, forms are not saved.
using inline formset . Book Title is saved by Author,1 author can have multiple books .
Codes below are what I am using:
This is the view , Posting content of Inlineformset , if it is get , shows the form , if it is post it must save the data .
#view.py
from django.shortcuts import render
# Create your views here.
from django.views.generic import TemplateView,UpdateView
from django.forms import inlineformset_factory
from .models import Author,Book
from django.shortcuts import redirect
class BookView(TemplateView):
template_name ="index.html"
def get(self, request, *args, **kwargs):
return self.render_preview(request)
def post(self,request,*args,**kwargs):
return self.save_book(request)
def save_book(self,request,**kwargs):
BookFormSet = inlineformset_factory(Author, Book, fields=('title',),can_delete=False)
author = Author.objects.get(name='John')
formset = BookFormSet(instance=author)
if formset.is_valid():
formset.save()
return redirect('/')
context = super(BookView, self).get_context_data(**kwargs)
context['formset'] = formset
return self.render_to_response(context)
def render_preview(self, request, **kwargs):
BookFormSet = inlineformset_factory(Author, Book, fields=('title',),can_delete=False)
author = Author.objects.get(name='John')
formset = BookFormSet(instance=author)
context = super(BookView, self).get_context_data(**kwargs)
context['formset'] = formset
return self.render_to_response(context)
#Models
This is the models.py
# Create your models here.
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
def __str__(self):
return self.title
index.html
<form action="" method="post" >
<div class="html">
{% csrf_token %}
{% for form in formset %}
{{form }}
{%endfor %}
<button type="submit">save</button>
</form>
</div>
I was having this same issue not so long ago and I solved it a bit different than by using a FormSet (which I am not so familiar with), hope it helps you out:
Models.py
from django.db import models
class Author(models.Model)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
def __str__(self):
return self.title
Forms.py
from django import forms
from Appname.models import Author, Book
class Author(forms.ModelForm):
class Meta:
model = Author
fields = ['name']
class Book(forms.ModelForm):
class Meta:
model = Book
fields = ['author', 'title']
Views.py
class BookView(TemplateView):
template_name ="index.html"
author_form_class = Author
def get_context_data(self, **kwargs):
context = super(BookView, self).get_context_data(**kwargs)
context['author_form'] = self.author_form_class
context['book_form'] = self.book_form_class
return context
def post(self, request):
author_form_class = Author(request.POST or None)
book_form_class = Book(request.POST or None)
if author_form_class.is_valid():
author_form_class.save()
return redirect('/')
if book_form_class.is_valid():
book_form_class.save()
return redirect('/')
index.html
<form method="post">
{{ csrf_token }}
{{ author_form}}
<input type="submit" value="submit">
</form>
I'm new to coding with django, and I'm trying to add comments to my blog app, but I'm having trouble with the validation of this form, it always returns False with form.is_valid(), so the object is never saved
views.py
def blog_post_detail_view(request, slug):
obj = get_object_or_404(BlogPost, slug=slug)
comments = Comment.objects.filter(blog_post=obj)
initial_data = {
"blog_post": obj,
}
form = CommentModelForm(request.POST or None, initial=initial_data)
if form.is_valid():
comment_obj = form.save(commit=False)
comment_obj.user = request.user
comment_obj.save()
form = CommentModelForm()
else:
print('not clean')
context = {
"object": obj,
"comments": comments,
"form": form,
}
template_name = 'blog/detail.html'
return render(request, template_name, context)
forms.py
from django import forms
from .models import Comment
class CommentModelForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content','blog_post']
HTML
<form method='POST' action='.'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Send</button>
</form>
models.py
class Comment(models.Model):
content = models.TextField(max_length=300)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, default=1)
blog_post = models.ForeignKey(BlogPost, null=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return str(self.user.username)
def __str__(self):
return str(self.user.username)
I would like to filter through the restaurants that the request.user has done. following the docs but i keep getting __init__() got an unexpected keyword argument user when I try to filter
forms.py
from .models import Restaurant
from .models import Item
from django import forms
class LocationCreate(forms.ModelForm):
class Meta:
model = Item
fields = [
'restaurant'
'category',
'food_name'
]
def __init__(self, user=None, *args, **kwargs):
super(ItemCreate, self).__init__(*args, **kwargs)
self.fields['restaurant'].queryset = Restaurant.objects.filter(owner=user)
models.py
class Restaurant(models.Model):
restaurant_name = models.CharField(max_length=250)
restaurant_photo = models.ImageField(upload_to='roote_image')
category = models.ManyToManyField(Category)
timestamp = models.DateTimeField(auto_now_add=True, null=True)
updated = models.DateTimeField(auto_now=True, null=True)
owner = models.ForeignKey(User)
def get_absolute_url(self):
return reverse('post:detail', kwargs={'pk': self.pk})
class Item(models.Model):
restaurant= models.ForeignKey(Roote, on_delete=models.CASCADE, null=True)
food_name = models.CharField(max_length=1000)
category = models.CharField(max_length=250)
owner = models.ForeignKey(User)
def __str__(self):
return self.food_name
def get_absolute_url(self):
return reverse('post:detail', kwargs={'pk': self.pk})
views.py
class ItemCreate(CreateView):
model = Item
fields = ['restaurant','category ', 'food_name ']
success_url = reverse_lazy('post:index')
def form_valid(self, form):
if form.is_valid():
roote = restaurant.objects.filter(owner =self.request.user)
instance = form.save(commit=False)
instance.owner = self.request.user
return super(ItemCreate, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(ItemCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
detail.html
{% block body %}
<div class="col-sm-6 col-sm-offset-3">
<img src="{{ restaurant.restaurant_photo.url }}" style="width: 250px;" >
<h1>{{ restaurant.restaurant_name }}</h1>
{% for item in restaurant.item_set.all %}
{{ item.food_name }}: {{ item.category}}
<br>
{% endfor %}
The form works without the filter but it brings in every instance of a restaurant that has been made in the web site
full error:
return form_class(**self.get_form_kwargs())
TypeError: __init__() got an unexpected keyword argument 'user'
You forgot to add form_class attribute to your view
class ItemCreate(CreateView):
model = Item
success_url = reverse_lazy('post:index')
form_class = LocationCreate # <- here
def form_valid(self, form):
if form.is_valid():
roote = restaurant.objects.filter(owner=self.request.user)
instance = form.save(commit=False)
instance.owner = self.request.user
return super(ItemCreate, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(ItemCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
According to the docs, this is the signature of the __init__ method for a ModelForm:
def __init__(self, *args, **kwargs)
See that there is no user? This is what the error message is telling you.
If you want to get the user, try via self.request.user (or something similiar, depending on what middlewares you use).
Hope that helps!