Django Converting to CBV + tests - python

I'm trying to test my app. I went over the documentation, and managed to make the test for my URL's and all views but one.
I'm having trouble converting it to a class view and I'm not really sure what kind of tests should I do here ? The documentation explained how it works, but I don't now where to go from here..
Anyone mind helping me out ?
here is the view that I'm trying to convert and test :
def add_comment_to_article(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.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={"article_id": article.pk}))
else:
form = CommentForm()
return render(request, 'news/add_comment_to_article.html', {'form': form})
my urls :
app_name = "news"
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:article_id>/', views.article_view, name='article'),
path('articles/', views.ArticlesView.as_view(), name='articles'),
path('search/', include('haystack.urls',)),
path('<int:pk>/comment/', views.CommentCreateView.as_view(), name='add_comment_to_article'),
#path('articles/<int:category_id>', views.CategoryView.as_view(), name="category")
]
my form:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text',)
The view is in charge of adding a comment to my Article post.
Thank you !!

So assuming you don't have the post field in your CommentForm, I'd maybe do something like this:
# views.py
from django.views.generic import CreateView
from .models import Comment
class CommentCreateView(CreateView):
model = Comment
form_class = CommentForm
template_name = 'my_template.html'
def form_valid(self, *args, **kwargs):
article = get_object_or_404(Article, pk=kwargs.get('pk'))
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={'article_id': article.pk}))
# tests.py
from django.tests import TestCase
class CreateCommentViewTestCase(TestCase):
def setUp(self):
# maybe look into factory boy if you haven't already
self.article = Article.objects.create()
def test_get(self):
response = self.client.get(reverse('news:add_comment_to_article', kwargs={'article_id': self.article.pk}))
self.assertEqual(response.status_code, 200)
def test_post(self):
# populate with form data
post_data = {'form_field': 'value'}
original_comment_count = self.article.comment_set.all().count()
response = self.client.post(reverse('news:add_comment_to_article', kwargs={'article_id': self.article.pk}))
new_comment_count = self.article.comment_set.all().count()
self.assertNotEqual(original_comment_count, new_comment_count)
self.assertEqual(response.status_code, 302)
django-webtest is pretty useful for testing CBVs too.

Related

type object 'Post' has no attribute 'filter'

Every time I attempt to write a comment on a post, I get an AttirbuteError at the post number. e.g- 'AttributeError at /post/54/', and below this it says 'type object 'Post' has no attribute 'filter''. It then directs me to my views.py line 58, which reads: post = self.get_object(Post). It is a part of my PostDetailClass:
class PostDetailView(DetailView):
model = Post
form = CommentForm
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object(Post)
form.instance. user = request.user
form.instance.post = post
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
reply = comment_qs, reply=None
form.save()
form.save_m2m()
return redirect(reverse("post", kwargs={
'content': Post.content
}))
urls.py
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.urls.conf import re_path
from django.views.generic.base import RedirectView
from .views import (
PostListView,
PostDetailView,
PostCreateView,
PostDeleteView,
UserPostListView,
TagIndexView,
about,
)
from . import views
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-
detail'),
path('user/<str:username>', UserPostListView.as_view(),
name='user-posts'),
path('post/new', PostCreateView.as_view(), name='post-create'),
path('about/', views.about, name='blog-about'),
path('map/', views.map, name='blog-map'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(),
name='post-delete'),
path('latest-posts/', views.latest_posts, name='latest-posts'),
path('focused/', views.focused, name='focused'),
path('snakegame/',views.snake_game, name='snake-game'),
re_path(r'^tag/(?P<slug>[-\w]*)/$',TagIndexView.as_view(),
name='tagged')
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Can anyone point out what is wrong with my code? Thank you.
The issue comes from passing Post to self.get_object(). get_object accepts a queryset as its argument. A queryset object would have .filter() but not Post.
In this case you actually don't need to pass anything to self.get_object. When you don't pass anything to it, the queryset defaults to self.get_queryset().
In short, change that line to:
post = self.get_object()
you should try to get the post object first like below :-
object_id = self.kwargs[self.pk_url_kwarg]
post = self.model.objects.get(id=object_id)
in your post method :-
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
object_id = self.kwargs[self.pk_url_kwarg]
post = self.model.objects.get(id=object_id)
form.instance.user = request.user
form.instance.post = post
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
reply = comment_qs, reply=None
form.save()
form.save_m2m()
return redirect(reverse("post", kwargs={
'content': post.content
}))

__init__() got an unexpected keyword argument 'user_id' in django

i have a form for update view like this:
class editpost(forms.ModelForm):
class Meta:
model = Posts
fields = ['body']
and a view like this:
#login_required
def post_edit(request, user_id, post_id):
if request.user.id == user_id:
post = get_object_or_404(Post, pk=post_id)
if request.method == 'POST':
form = editpost(request.POST, instance=post)
if form.is_valid():
ep = form.save(commit=False)
ep.slug = slugify(form.cleaned_data['body'][:30])
ep.save()
messages.success(request, 'your post edited successfully', 'success')
return redirect('account:dashboard', user_id)
else:
form = EditPostForm(instance=post)
return render(request, 'editpost.html', {'form':form})
else:
return redirect('Posts:index')
and url.py like this:
from django.urls import path
from . import views
app_name = 'Posts'
urlpatterns = [
path('', views.index.as_view(),name='index'),
path('<int:year>/<int:month>/<int:day>/<slug:slug>', views.detailPost,name='detail'),
path('addpost/<int:user_id>', views.addpost,name='addpost'),
path('delpost/<int:user_id>/<int:post_id>', views.delpost,name='delpost'),
path('editpost/<int:user_id>/<int:post_id>', views.editpost,name='editpost'),
]
when i open editpost url i got this error,what should i do to fix it?
The path for editpost should point to your view method: views.post_edit not views.editpost
path('editpost/<int:user_id>/<int:post_id>', views.post_edit, name='editpost'),

Class view with post, get and data

I have practically finished the microblogging application and I want to change to class base views now. I read about generic views, but it looks like each of them has specific properties. In my index view I display two models and two forms, so I created IndexView (View) and put the get and post functions there and it works, but it doesn't look like there are any benefits of class views, so I'm probably doing something wrong. Currently, several views repeat a lot of code, and I thought that class views would solve this problem. I would like an example of how such a view should be written as class view with reusable get.
class IndexView(View):
def get(self, request, *args, **kwargs):
post_form = AddPostForm()
comment_form = AddCommentForm()
if request.user.is_authenticated:
logged_user = CustomUser.objects.get(id=request.user.id)
blocked_users = logged_user.blocked.all()
posts = Post.objects.exclude(author__in=blocked_users)
print(blocked_users)
posts = posts.order_by('-pub_date')
else:
posts = Post.objects.all()
posts = posts.order_by('-pub_date')
comments = Comment.objects.all()
comments = comments.order_by("-pub_date")
return render(request, 'mikroblog/index.html', {'posts': posts, 'comments': comments,
'post_form': post_form, 'comment_form': comment_form})
def post(self, request, *args, **kwargs):
if request.method == 'POST':
post_form = AddPostForm(request.POST)
comment_form = AddCommentForm(request.POST)
if post_form.is_valid():
new_post = post_form.save(commit=False)
new_post.author = request.user
new_post.tags = ''
content = ''
for word in new_post.content_post.split():
if '#' in word:
new_post.tags += f"{word} "
content += f"{word} "
else:
content += f"{word} "
new_post.content_post = content
new_post.save()
for word in content.split():
if '#' in word:
print(word)
user_to_notificate = CustomUser.objects.get(username=word[1:])
new_notification = TalkAbout()
new_notification.where = new_post
new_notification._from = new_post.author
new_notification.to = user_to_notificate
new_notification.sended = False
print(new_notification)
new_notification.save()
post_form = AddPostForm()
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.author = request.user
new_comment.save()
comment_form = AddCommentForm()
return HttpResponseRedirect(reverse('index'))
Use class-based views and override the 'post' and 'get' methods if you need to.
Follow this link:
Django CBVs documentation

How can I add a favorite article marking system with django generic class-based view?

I'm trying to let my users mark favorite posts that they can read later.
I've seen some solutions using FBV, but I want to make favorite markers with CBV.
How can I do it using django class-based view (DetailView) ?
model
class Article(models.Model):
...
favorite = models.ManyToManyField(get_user_model(), related_name='favorite', blank=True)
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
views
class ArticleDetailView(ObjectViewMixin, DetailView):
model = Article
context_object_name = 'article'
...
def get_context_data(self, **kwargs):
...
return context
def favorite_post(request, id):
post = get_object_or_404(Article, id=id)
if post.favorite.filter(id=request.user.id).exists():
post.favorite.remove(request.user)
else:
post.favorite.add(request.user)
return redirect('article_detail', pk=article.pk)
urls
urlpatterns = [
path('<int:pk>/edit/', ArticleUpdateView.as_view(), name='article_update'),
path('<int:pk>/favorite_post/', favorite_post, name='favorite_post'),
]
You could try to use the UpdateView and override its post() method; you can use its .get_object() method the get the current Article instance.
views.py
from django.views.generic import UpdateView
from .models import Article
class MyUpdateView(UpdateView):
http_method_names = ['post', ]
model = Article
def post(self, *args, **kwargs):
self.object = self.get_object()
if self.object.favorite.filter(id=request.user.id).exists():
self.object.favorite.remove(request.user)
else:
self.object.favorite.add(request.user)
return redirect('article_detail', pk=self.object.pk)
urls.py
urlpatterns = [
...
path('<int:pk>/favorite_post/', MyUpdateView.as_view(), name='favorite_post'),
]
Does that work for you?

How to include an object from another model in a generic Django CreateView?

Ok so I am trying to include the corrosponding Comment in my createAnswer View currently the url of the createAnswer page includes the pk ok the right comment so i need to get the comment by the id in the url.
My generic CreateView looks like this:
class createAnswer(CreateView):
model = Answer
fields = ['content']
def getComment(self, request):
???
comment = getComment()
def get_success_url(self):
this_path = self.request.get_full_path()
path_list = this_path.split('/')
def get_comment_id(self):
for i in range(len(path_list)):
if path_list[i].isdigit():
return path_list[i]
return '/blogcontact/comment/'+ get_comment_id(self)
def form_valid(self,form):
this_path = self.request.get_full_path()
path_list = this_path.split('/')
def get_comment_id(self):
for i in range(len(path_list)):
if path_list[i].isdigit():
return path_list[i]
form.instance.author = self.request.user
form.instance.comment = Comment.objects.get(id=get_comment_id(self))
return super().form_valid(form)
My Urls.py looks like this:
from django.urls import path
from . import views
from .views import createAnswer
urlpatterns = [
path('contact/comment/<int:pk>/newanswer', createAnswer.as_view(),
name='answer-create')
]<br>
I would like to save the Comment object in a variable so i Can use it in the html template like this {{comment}}
I think you you are confusing the function views and the Class Based Views (CBV), and you never import a request, it is just a parameter your views receive.
In a function view you do the following:
def my_view(request):
if request.method == 'POST':
# do some stuff
For CBV each method is a function:
from django.views.generic.edit import CreateView
class MyView(CreateView):
model = Answer
fields = ['content']
def get(self, request):
# do some stuff
def post(self, request):
# do some stuff
EDIT: To access the url parameters in class based views use self.kwargs, so you would access the comment pk by doing self.kwargs['pk']. Now you just need to get the comment and add it to the context data:
class CreateAnswer(CreateView):
model = Answer
fields = ['content']
def get_context_data(self, **kwargs):
kwargs['comment'] = Comment.objects.get(pk=self.kwargs['pk'])
return super().get_context_data(**kwargs)
def form_valid(self, form):
# do some stuff

Categories