I am trying to allow user to post answer and comments to a particular bloq question
Q&A
Error displayed:
ValueError at /adding-url-patterns-for-views
Cannot assign "<SimpleLazyObject<django.contrib.auth.models.AnonymousUser object at 0x04A5B370>>": "PostAnswer.user" must be a "CustomUser" instance.
Here is my model
class PostQuestion(models.Model):
""" Model for posting questions """
title = models.CharField(max_length=100, unique=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, null=True, blank=True)
slug = models.SlugField(unique=True, null=True, max_length=250)
created_on = models.DateTimeField('date published',
auto_now_add=True)
text_content = models.TextField()
tags = TaggableManager()
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('detail_view', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
return super().save(*args, **kwargs)
class PostAnswer(models.Model):
""" Model for answering questions"""
question = models.ForeignKey(
PostQuestion,
on_delete=models.CASCADE,
related_name='comments',
)
text_content = models.TextField()
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
)
approved_comment = models.BooleanField(default=False)
created_on = models.DateTimeField('published', auto_now_add=True)
class Meta:
ordering = ['created_on']
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return 'comment by {} on {}'.format(self.user, self.question)
Here is my views.py on an app
class Index(ListView):
queryset = PostQuestion.objects.all()
template_name = 'index.html'
context_object_name = 'posts'
paginate_by = 10
def detail(request, slug):
post = get_object_or_404(PostQuestion, slug=slug)
comments = post.comments.all()
# comment posted
if request.method == 'POST':
form = CommentForm(data=request.POST)
if form.is_valid():
form.instance.question = post
form.instance.user = request.user
form.save()
return redirect('post_detail', slug=slug)
else:
form = CommentForm()
context = {'post': post,
'comments': comments,
'form': form}
return render(request, 'detail.html', context)
My App form:
class CommentForm(forms.ModelForm):
text_content = forms.CharField(widget=PagedownWidget())
class Meta:
model = PostAnswer
fields = ['text_content']
My urls.py view for the app
from .views import detail, ask, Index
from django.urls import path
urlpatterns = [
# path('<slug:slug>/comment', add_comment_to_post, name='add_comment_to_post'),
path('ask/', ask, name='ask'),
path('<slug:slug>', detail, name='detail_view'),
path('', Index.as_view(), name='index'),
]
Here is the html template for the comment
<!--------Display a form for users to add a new comment------->
<h3>Leave a comment</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary btn-sm">Add Comment</button>
</form>
This is all of code that I think you need to know the problem. I don't know if the problem can be caused for database or why are not all instructions in the views.
If you can help me, thanks you in advance.
The name of the field is question, not post, so you should rewrite the logic to:
new_comment.question = post
This will however still raise an error, since the user field is not filled in either.
Using commit=False is however not a good idea, since then the form can no longer save many-to-many fields. As far as I can see, there are at the moment no many-to-many fields, but nevertheless, if you later add such fields, you have to rewrite the logic. You can improve the view to:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def detail(request, slug):
post = get_object_or_404(PostQuestion, slug=slug)
comments = post.comments.all()
if request.method == 'POST':
form = CommentForm(data=request.POST)
if form.is_valid():
form.instance.question = post
form.instance.user = request.user
form.save()
return redirect('page:detail_view', slug=slug)
else:
form = CommentForm()
context = {
'post': post,
'comments': comments,
'form': form
}
return render(request, 'detail.html', context)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Related
I want to make a form for adding comments, but I only display a form without fields for input, it does not give any errors.
how it looks on the web-site, there should be a form to post a comment.
I would also like to know how this code can be improved.
Any help is appreciated!
Here is my forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['body']
views.py:
def comments(request, id):
if request.method == 'POST':
cf = CommentForm(request.POST or None)
if cf.is_valid():
body = request.POST.get('body')
comment = Comment.objects.create(post=post, user=request.name, body=body)
comment.save()
return redirect(post.get_absolute_url())
else:
cf = CommentForm()
context = {
'cf': cf,
}
return render(request, 'main/article_detail.html', context)
urls.py:
from django.urls import path, include
from . import views
from .views import *
urlpatterns = [
path('', PostListViews.as_view(), name='articles'),
path('article/<int:pk>/', PostDetailViews.as_view(), name='articles-detail'),
path('register/', register, name='register'),
path('login/', login, name='login'),
]
My form in template:
<form method="POST">
{% csrf_token %}
{{ cf.as_p }}
<button type="submit" class="btn btn-primary" style="width: 100px;position: relative;">Submit</button>
</form>
my models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Article(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Article, related_name='comments', on_delete=models.CASCADE)
name = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['date']
def __str__(self):
return f'Comment {self.body} by {self.name}'
In your views.py
def comments(request, id):
if request.method == 'POST':
cf = CommentForm(request.POST or None)
if cf.is_valid():
comment = cf.save(commit=False)
comment.user = request.user
comment.post = post # you also will have to query your post first (i presume thats what the id is for in the function declaration)
comment.save()
return redirect(post.get_absolute_url())
else: # fix this
cf = CommentForm()
context = {
'cf': cf,
}
return render(request, 'main/article_detail.html', context) # fix this
Your indentation is implying that the template will render only with the POST method, since when you're accessing the view you are calling a GET method, your render function never gets called.
Also just use regular form.save() method to save your objects to database, its much easier to understand. And you do have to query your post before assigning a comment to it.
In your views, your else block is not indented with request.method if block and also use return render method...
Also use data=request.post in your parentheses bracket instead of "request.post or none". Try this....it should work then....
I imagine the solution to this can be found i similar topics, however, it seems a little bit different from problems I have seen.
The redirect, in add_post view, should send me to the Post class, where I have a get_absolute_url.
But it happens that I get the following error message: get_absolute_url() missing 1 required positional argument: 'self'
I literally copied the coded form the "Django Projects Cookbook" in case you wanna check it out and try the code yourself.
Thank you.
from django.contrib.auth.decorators import user_passes_test
from django.shortcuts import redirect, render_to_response, get_object_or_404, render
from .models import Post
from .forms import PostForm, CommentForm
#user_passes_test(lambda u: u.is_superuser)
def add_post(request):
form = PostForm(request.POST or None)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect(Post) # redirect to Post class
return render(request, 'blog/add_post.html',{ 'form': form })
def view_post(request, slug):
post = get_object_or_404(Post, slug=slug)
form = CommentForm(request.POST or None)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect(request.path)
return render(request, 'blog/blog_post.html',{'post': post,'form': form,})
from django.db import models
from django.template.defaultfilters import slugify
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
text = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __unicode__(self):
return self.title
#models.permalink
def get_absolute_url(self): # get_absolute_url
return ('blog_post_detail', (), { 'slug' :self.slug, })
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
class Comment(models.Model):
name = models.CharField(max_length=42)
email = models.EmailField(max_length=75)
website = models.URLField(max_length=200, null=True, blank=True)
text = models.TextField()
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.text
You use the Post class, not its object to redirect. You thus need to use post in:
#user_passes_test(lambda u: u.is_superuser)
def add_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.instance.author = request.user
post = form.save()
return redirect(post)
return render(request, 'blog/add_post.html',{ 'form': form })
The #permalink decorator was removed in Django-2.1, it is advised to use reverse [Django-doc] instead:
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
text = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return reverse('blog_post_detail', kwargs={'slug': self.slug})
Furthermore python-2.x is no longer supported since January 1, 2020; so you might want to update to django-3.x in a python-3.x environment.
how can I fix this issue?
the page runs perfectly. when I do post the post. it posts but when I want to type the comment and send by 'GET' I get this error. so, how can I ignore this error this my first question?
- also, I need anyone to give me the best way to make a relationship between post and comment
models.py
from django.db import models
from django.contrib.auth.models import User
class Publication(models.Model):
title = models.CharField(max_length=30)
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class Article(models.Model):
publications = models.ManyToManyField(Publication)
headline = models.CharField(max_length=100)
class Meta:
ordering = ['headline']
def __str__(self):
return self.headline
class Post(models.Model):
users = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
question = models.TextField(max_length=500)
def __str__(self):
return self.title
class Comment(models.Model):
posts = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.TextField(max_length=500)
def __str__(self):
return self.comment
views.py
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from .models import Post, Comment
from .forms import PostForm, CommentForm
def index(request):
# All questions
posts = Post.objects.all()
return render(request, 'community/index.html', {'posts': posts})
def post_view(request):
post_form = PostForm
context = {'posted': post_form}
# Create post
if request.method == 'GET':
post_form = PostForm(request.GET)
if post_form.is_valid():
user_post = post_form.save(commit=False)
user_post.title = post_form.cleaned_data['title']
user_post.question = post_form.cleaned_data['question']
post = Post.objects.create(users=User.objects.get(username=request.user), title=user_post.title, question=user_post.question)
post.save()
return redirect('community:index')
return render(request, 'community/post.html', context)
def answers(request, post_id):
# Specific post
posts = Post.objects.get(id=post_id)
# Create comment
comment_form = CommentForm
context = {'posts': posts, 'comment_form': comment_form}
if request.method == 'GET':
comment_form = CommentForm(request.GET)
if comment_form.is_valid():
user_comment = comment_form.save(commit=False)
user_comment.comment = comment_form.cleaned_data['comment']
user_comment.save()
return render(request, 'community/answers.html', context)
urls.py
from django.urls import path
from . import views
app_name = 'community'
urlpatterns = [
path('', views.index, name='index'),
path('post/', views.post_view, name='post'),
path('answers/<int:post_id>', views.answers, name='answers'),
]
forms.py
from .models import Post, Comment
from django import forms
from django.contrib.auth.models import User
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'
exclude = ['users']
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = '__all__'
exclude = ['users', 'posts']
You forgot to assign the post, or the post_id of the comment you created:
def answers(request, post_id):
# Specific post
posts = Post.objects.get(id=post_id)
# Create comment
comment_form = CommentForm
context = {'posts': posts, 'comment_form': comment_form}
if request.method == 'GET':
comment_form = CommentForm(request.GET)
if comment_form.is_valid():
comment_form.instance.post_id = post_id
user_comment = comment_form.save()
# …
That being said, the above view does not really respect the HTTP assumptions. A view that makes a GET request is not supposed to change entities, so if you want to create a comment, you need to do that through a POST request. Furthemore in order to implement the Post/Redirect/Get pattern [wiki] a successful POST request should return a redirect response.
I have followed Django Girls tuorial http://tutorial.djangogirls.org/en/index.html and successfully created a blog. However I wanted to add an image field to my block,
Currently my models.py looks like:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length = 200)
text = models.TextField()
model_pic= models.ImageField(upload_to = 'blog/images', default='blog/images/already.png')
created_date = models.DateTimeField(default = timezone.now)
published_date = models.DateTimeField(blank = True, null =True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
def approved_comment(self):
return self.comments.filter(approved_comment=True)
class Comment(models.Model):
post = models.ForeignKey('blog.Post', related_name='comments')
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
and my views.py looks like:
from django.shortcuts import render, get_object_or_404 ,redirect
from .models import Post, Comment
from django.utils import timezone
from .forms import PostForm, CommentForm
from django.contrib.auth.decorators import login_required
def post_list(request):
posts = Post.objects.filter(published_date__lte = timezone.now()).order_by('published_date')
return render(request, 'blog/post_list.html',{'posts' : posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk = pk)
return render(request, 'blog/post_detail.html',{'post':post})
#login_required
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit = False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('blog.views.post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
#login_required
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('blog.views.post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {'form': form})
#login_required
def post_draft_list(request):
posts = Post.objects.filter(published_date__isnull=True).order_by('created_date')
return render(request, 'blog/post_draft_list.html',{'posts':posts})
#login_required
def post_publish(request, pk):
post = get_object_or_404(Post, pk=pk)
post.publish()
return redirect('blog.views.post_detail', pk=pk)
#login_required
def post_remove(request, pk):
post = get_object_or_404(Post, pk=pk)
post.delete()
return redirect('blog.views.post_list')
def add_comment_to_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit = False)
comment.post = post
comment.save()
return redirect('blog.views.post_detail', pk=post.pk)
else:
form = CommentForm()
return render(request,'blog/add_comment_to_post.html', {'form': form})
#login_required
def comment_approve(request, pk):
comment = get_object_or_404(Comment, pk=pk)
comment.approve()
return redirect('blog.views.post_detail', pk=comment.post.pk)
#login_required
def comment_remove(request, pk):
comment = get_object_or_404(Comment, pk=pk)
post_pk = comment.post.pk
comment.delete()
return redirect('blog.views.post_detail', pk=post_pk)
and my forms.py appears like :
from django import forms
from .models import Post, Comment
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text')
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text',)
Now Please guide me what changes should I make in model Post and how should I modify the view to get my image rendered and how should I make the template for same
I tried https://coderwall.com/p/bz0sng/simple-django-image-upload-to-model-imagefield but It didn't worked for me.
Any help will be highly appreciated.
Thank You.
Had you need any more information please comment it.
So, let's go.
When you set a ImageField(), you have a upload_to attribute. From the documentation:
A local filesystem path that will be appended to your MEDIA_ROOT setting to determine the value of the url attribute.
So you will have a function to determine a dynamic path where the image will be stored (read the link above for more information).
E.g.
class Post(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length = 200)
text = models.TextField()
model_pic= models.ImageField(upload_to=upload_image, default='blog/images/already.png')
created_date = models.DateTimeField(default = timezone.now)
published_date = models.DateTimeField(blank = True, null =True)
def upload_image(self, filename):
return 'post/{}/{}'.format(self.title, filename)
Note that you don't include the model_pic in your fields of the PostForm. So it will not appear on the template.
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text', 'model_pic',)
Your view will change a little bit:
#login_required
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST, request.FILES)
Once you're using an upload, you will use the request.FILES read more here, but let me quote this:
Note that FILES will only contain data if the request method was POST and the that posted to the request had enctype="multipart/form-data". Otherwise, FILES will be a blank dictionary-like object.
So on your template, your tag <form> will have a attribute enctype="multipart/form-data"
<form enctype="multipart/form-data" method="POST" action="">
{{ form.as_p }}
</form>
After all, you can retrieve this image to show in a page. Assuming that you have passed a post instance via context to the template:
<img src="{{ post.model_pic.url }}" alt="{{ post.title }}" />
i create simple page where i can add article (title, text and category). When i do it on 'site administration everything is ok', but when i add it on page i can't choose category.
http://i.stack.imgur.com/4Nxzb.jpg
I choose category, for example like on this screen but my article do not have this category after i save it (article is uncategorized).
I created forms.py file and i done it like this:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text', 'categories')
How must i change 'categories' here to make it usable?
models.py:
from django.db import models
from django.utils import timezone
class Category(models.Model):
name = models.CharField('Nazwa Kategorii', max_length=100)
slug = models.SlugField('Odnośnik', max_length=100)
icon = models.ImageField('Ikonka Kategorii', upload_to='icons',
blank=True)
class Meta:
verbose_name = "Kategoria"
verbose_name_plural = "Kategorie"
def __unicode__(self):
return self.name
class Post(models.Model):
author = models.CharField(max_length=25, blank=True, null=True)
title = models.CharField(max_length=200)
slug = models.SlugField('Odnośnik', max_length=255)
text = models.TextField()
published_date = models.DateTimeField(
default=timezone.now)
categories = models.ManyToManyField(Category, verbose_name='Kategorie')
def publish(self):
self.published_date = timezone.now()
self.save()
def __unicode__(self):
return self.title
And from views.py my view
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('blog.views.post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
When you save a form using commit=False you should call the save_m2m() method of the form:
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m()
Another, more elegant, solution is to pass the instance with preset field to the form's constructor. Is this case you don't need to use the commit=False argument:
form = PostForm(request.POST, instance=Post(author=request.user))
if form.is_valid():
post = form.save()
return redirect('blog.views.post_detail', pk=post.pk)