Django DetailView occurs an error? - django

I Implemented very simple DetailView in Django 1.9.5:
class PostDetailView(DetailView):
Model = Post
template_name = "post/detail.html"
urls.py
from django.conf.urls import url
from chacha_dabang.views import *
urlpatterns = [
url(r'^$', PostListView.as_view(), name="post_list"),
url(r'^new/$', post_new, name="post_new"),
url(r'^(?P<pk>\d+)/$', PostDetailView.as_view(), name="post_detail"),
url(r'^(?P<pk>\d+)/edit$', post_edit, name="post_edit"),
url(r'^(?P<pk>\d+)/delete$', post_delete, name="post_delete"),
url(r'^(?P<pk>\d+)/comment/new/$', comment_new, name="comment_new"),
url(r'^(?P<pk>\d+)/comment/(?P<comment_pk>\d+)/edit$', comment_edit, name="comment_edit"),
]
Errors :
I don't know why it says I have to override query_set(). (As I Know, DetailView automatically set query according to pk)
If I used Function Based View like below,
def post_detail(request, pk):
post = Post.objects.get(pk=pk)
return render(
request,
'post/detail.html',
{
'post': post,
}
)
It totally works fine. Need your helps.

You capitalized model. It is not Model, it should be model.

Related

how to configure redirect in DeleteView

when i try to delete some comment it first take me to the comment_delete_confirm.html, then redirect to the page what linked in success_url = '/blog/'. Problem become when i change success_url to something like 'post-detail' (becose i want after comment_delete_confirm return to the post), it cannot find this page, becose in the brauser url it looks like that: '127.0.0.1:8000/blog/post/18/comment_delete/post-detail'
this is my views.py and urls.py files:
class CommentDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Comment
success_url = 'post-detail'
# only the author can delete his post
# if not author try to delete post it gives 403 forbidden
def test_func(self):
comment = self.get_object()
if self.request.user == comment.user:
return True
return False
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
path('post/<int:pk>/comment/', add_comment, name='comment-create'),
path('post/<int:pk>/comment_update/', comment_update, name='comment-update'),
path('post/<int:pk>/comment_delete/', CommentDeleteView.as_view(), name='comment-delete')
]
You need to use the actual URL, not the name. You can use reverse_lazy for this.
from django.urls import reverse_lazy
class CommentDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Comment
success_url = reverse_lazy('post-detail')
Edit
If you need dynamic data, you can override the get_success_url method instead of defining the attribute directly. There you have access to self.object (because the method is called before the deletion is actually processed). Assuming your comment object has a post field which is a foreign key to Post:
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.object.post_id})

How to test get_success_url in CreateView in Django

I am trying to write a test for the get_success_url method in a CreateView, to make sure it redirects to the newly created page. But the response status code is 405 instead of 302 as I expected.
views.py
class BlogCreate(CreateView):
model = Blog
fields = [‘author’, 'title', ’post’]
def get_success_url(self):
return reverse_lazy('blog:blog-detail', kwargs={'slug': self.object.slug})
class BlogList(ListView):
model = Blog
ordering = ["-created"]
class BlogDetail(DetailView):
model = Blog
config/urls.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('blog.url', namespace='blog')),
blog/urls.py
from django.conf.urls import url
from .views import BlogCreate, BlogList, BlogDetail, BlogEdit, BlogDelete
urlpatterns = [
url(r'^(?P<slug>[-\w]+)/$', BlogDetail.as_view(), name='blog-detail'),
url(r'^(?P<slug>[-\w]+)/edit$', BlogEdit.as_view(), name='blog-edit'),
url(r'^(?P<slug>[-\w]+)/delete$', BlogDelete.as_view(), name='blog-delete'),
url(r'^new$', BlogCreate.as_view(), name='blog-create'),
url(r'^$', BlogList.as_view(), name='blog-list'),
]
tests.py
class BlogCreateTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='john', password='123')
def test_create_success_url(self):
post = {‘author’: self.user,
'title': ‘new blog’,
‘article’: ‘text’,
}
response = self.client.post('/blog/new/', post)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, 'blog/new-blog/‘)
Are you trying to do a client.post() in your tests?
Remove the trailing slash from /blog/new/

PostList() received an invalid keyword 'template_name'

as_view only accepts arguments that are already attributes of the class
This make no sense to me since template_name is an attribute. I checked for similar questions but couldn't find an indication of where I went wrong with the code. Here is my code.
urls.py:
from django.conf.urls import url
from .views import PostList
urlpatterns = [
url(r'^$',
PostList.as_view(
template_name='blog/post_list.html'),
name='blog_post_list'),
]
views.py
from django.views.generic import View
from .models import Post
class PostList(View):
def get(self, request):
return render(
request,
'blog/post_list.html',
{'post_list': Post.objects.all()})
The View class, doesn't have a template_name attribute, you either want to use TemplateView, or it would make more sense to use a ListView
class PostList(ListView):
context_object_name = 'post_list'
template_name = 'blog/post_list.html'
def get_queryset(self):
return Post.objects.all()
Note: Either way, since you already set the template name in your view, you don't actually need to include it in the call to as_view anyway

Setting up Mimetype when using TemplateView in Django

Does anyboy know how do I set the desired mimetype when using TemplateView, as in:
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html')),
In this case, I want to set the mimtype as "text/plain"
For Django >= 1.5
TemplateView accepts a content_type argument.
Coping example from #Meilo
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html', content_type='text/plain')),
For Django < 1.5
I think that just calling TemplateView.as_view() is not posible but maybe i missed it (from the source),
but you can do your own class
class TextTemplateView(TemplateView):
def render_to_response(self, context, **response_kwargs):
response_kwargs['content_type'] = 'text/plain'
return super(TemplateView, self).render_to_response(context, **response_kwargs)
You can take a look to:
django.template.response => TemplateResponse
django.views.generic.base => TemplateView
And if you need something more dynamic:
from django.utils.decorators import classonlymethod
class ContentTypeTemplateView(TemplateView):
#classonlymethod
def as_view(cls, content_type='text/plain', **initargs):
setattr(cls, 'content_type', content_type)
return super(ContentTypeTemplateView, cls).as_view(**initargs)
def render_to_response(self, context, **response_kwargs):
response_kwargs['content_type'] = self.content_type
return super(ContentTypeTemplateView, self).render_to_response(context, **response_kwargs)
urlpatterns = patterns('',
url(r'^$', ContentTypeTemplateView.as_view(content_type='text/plain',
template_name='staticpages/test.html'),
name='index'),
)
Using a Mixin
from django.core.exceptions import ImproperlyConfigured
class ContentTypeMixin(object):
content_type = None
def render_to_response(self, context, **response_kwargs):
if not self.content_type:
raise ImproperlyConfigured(
"MimeTypeMixin rquires a definition of content_type")
response_kwargs['content_type'] = self.content_type
return super(ContentTypeMixin, self).render_to_response(context,
**response_kwargs)
class MyTxtView(ContentTypeMixin, TemplateView):
content_type = 'text/plain'
....
In Django 1.5 the content_type argument in the TemplateView adds the same functionality that was in the function-based view before. That makes it easier to set the desired mimetype:
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html', content_type='text/plain')),
I know that you ask for setting a content type with TemplateView, but I will give you different answer which I think that will be more clean and can be used in Django versions lower than 1.5.
url(r'^robots\.txt$', 'django.shortcuts.render', kwargs={
'template_name': 'robots.txt',
'content_type': 'text/plain',
})
With this approach you don't need to import anything or to subclass TemplateView and make ugly overwrites of some methods. You can simply use the old technique with function based views.
If you don't want to extend the TemplateView, you can extend the TemplateResponse to set the mimetype:
from django.template.response import TemplateResponse
class TextResponse(TemplateResponse):
def __init__(self, *args, **kwargs):
kwargs['mimetype'] = 'text/plain'
return super(TextResponse, self).__init__(*args, **kwargs)
Then pass it as the template_class to the TemplateView
urlpatterns = patterns('django.views.generic.simple',
(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', response_class=TextResponse)),
)
The best way to do it is to subclass TemplateView and override the render_to_response() method:
class StaticPagesTest(TemplateView):
template_name = 'staticpages/test.html'
def render_to_response(self, context, **kwargs):
return super(StaticPagesTest, self).render_to_response(context,
mimetype='text/plain', **kwargs)
I know this is solved for 1.5, but the application I am working in is 1.4.
I had an issue with two url patterns in a row using sacabuche's answer:
url(r'^playlist1\.m3u$', ContentTypeTemplateView.as_view(template_name='playlist1.m3u', content_type='audio/x-mpegurl')),
url(r'^playlist2\.pls$', ContentTypeTemplateView.as_view(template_name='playlist2.pls', content_type='audio/x-scpls'))
I found playlist1 would return the correct template, but with playlist2's content type! Playlist2 was ok. Adding a 3rd url pattern with a content-type of 'foo' would cause all playlist views to return with content-type 'foo'.
I ended up using the render method instead with good results:
urls:
url(r'^playlist1\.m3u$', 'content_type_to_template', {'template_name': 'playlist1.m3u', 'content_type': 'audio/x-mpegurl'}),
url(r'^playlist2\.pls$', 'content_type_to_template', {'template_name': 'playlist2.pls', 'content_type':'audio/x-scpls'})
views:
from django.shortcuts import render
def content_type_to_template(request, template_name='', content_type='text/plain'):
return render(request, template_name, content_type=content_type)
A simple example of how change content type of a TemplateView:
#views.py
from django.views.generic import TemplateView
class HomeView(TemplateView):
template_name = "home/index.html"
content_type = 'text/html'
# urls.py
url(r'^home/$', HomeView.as_view(), name='home_page'),
url(r'^test/(?P<template>.*)', lambda request, template: TemplateView.as_view(template_name=template)(request)),

Django: routing same model but different category field to separate URLs

I have the following model and url routes. There is one Post model that I want to route to different URLs based on the category. Is there a way to do this by passing in extra information in app/urls.py?
In app/posts/models.py
class Post(models.Model):
author = ...
title = ...
body = ...
category = models.CharField()
In app/urls.py
urlpatterns = patterns(
'',
(r'^blog/', include('posts.urls'), {'category': 'blog'}),
(r'^school/', include('posts.urls'), {'category': 'school'}),
)
My understanding is that the extra info from app/urls.py is included in each url route in app/posts/urls.py. Is there a way to use that information? What can I put in place of the exclamation points below?
In app/posts/urls.py
from models import Post
queryset = Post.objects.order_by('-pub_date')
urlpatterns = patterns(
'django.views.generic.list_detail',
url(r'^$', 'object_list',
{'queryset': queryset.filter(category=!!!!!!)}
name="postRoot"),
url(r'^(?P<slug>[-\w]+)/$', 'object_detail',
{'queryset': queryset.filter(category=!!!!!!)},
name="postDetail")
)
Thanks, joe
I am not aware of a way to use the URL parameters the way you have indicated. If anyone knows better, do correct me.
I faced a similar situation some time ago and made do with a thin wrapper over the list_detail view.
# views.py
from django.views.generic.list_detail import object_list
def object_list_wrapper(*args, **kwargs):
category = kwargs.pop('category')
queryset = Post.objects.filter(category = category)
kwargs['queryset'] = queryset
return object_list(*args, **kwargs)
#urls.py
urlpatterns = patterns('myapp.views',
url(r'^$', 'object_list_wrapper', {}, name="postRoot"),
...