How can i use csrf token in customised django model admin? - django

I am working in a simple Django project, and i want to add a button for every model object in admin and i am able to create it by using this:
in admin.py
class RegistrationAdmin(admin.ModelAdmin):
def button(self, obj):
isreg = obj.username
return format_html('<form action="/validate/" method="post">{% csrf_token %}<script>x={{isreg}};</script><button class="btn btn--pill btn--green"'
' type="submit">Validate</button></form>', {'isreg': isreg})
button.short_description = 'Action'
button.allow_tags = True
list_display = ['username', 'button']
But when i excute it it gives key error:
KeyError at /admin/myapp/registration/
'% csrf_token %'
so how can resolve this error?
or
is there any other way to add functionality to my validate button?

If you are talking about creating a new action for every instance, you can do something like this:
from django.conf.urls import url
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.utils.html import format_html
class RegistrationAdmin(admin.ModelAdmin):
list_display = ['username', 'button']
def button(self, obj):
return format_html('{}', obj.id, obj.username)
def get_urls(self):
urls = super().get_urls()
my_urls = [
url(r'^new-action/(?P<id>[0-9]+)$', self.new_action)
]
return my_urls + urls
def new_action(self, request, id):
if request.user.is_authenticated:
# your stuff
self.message_user(request, 'ID {} successfully processed'.format(id))
return HttpResponseRedirect('/admin')

solution provided by #Danilo Akamine has worked comletely fine for me.
but those who have same problem may require there:
url method in:
my_urls = [
url(r'^new-action/(?P<id>[0-9]+)$', self.new_action)
]
belongs to
django.conf.urls
so add this line to admin.py:
from django.conf.urls import url
or you can also use path method from django.urls as:
my_urls = [
path('new-action/<int:id>', self.new_action)
]
for more info:
visit https://docs.djangoproject.com/en/2.2/topics/http/urls/

Related

How to redirect an UpdateView upon success?

I created a small Django application to manage data that fits a simple a model. For now I only need two views: one to list all records and another to edit a record with a generic form. Everything functions as expected, except the redirection from the edit view upon a successful update. In urls.py are the following contents:
from django.urls import path
from . import views
app_name = 'reqs'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.ReqUpdateView.as_view(), name='update'),
]
In forms.py:
from django.forms import ModelForm
from .models import Requirement
class RequirementForm(ModelForm):
class Meta:
model = Requirement
fields = ['name', 'priority', 'source' , 'rationale']
And the templeate requirement_form.html:
<h1>{{ requirement.id }} - {{ requirement.name }}</h1>
<form method="post" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
<tr><td></td><td><button type="submit">Save</button></td></tr>
</table>
</form>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<br><br>
Back to list
Finally views.py, on a first attempt to redirect the update to the list:
from django.views.generic import ListView, UpdateView
from django.urls import reverse_lazy
from .models import Requirement
from .forms import RequirementForm
class IndexView(ListView):
template_name = 'reqs/index.html'
context_object_name = 'requirements_list'
def get_queryset(self):
return Requirement.objects.order_by('subject')
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
success_url = reverse_lazy('/')
With this formulation the Save button produces this error:
Reverse for '/' not found. '/' is not a valid view function or pattern name.
I also tried an empty string as argument to reverse_lazy, as well as the path name index, but a similar error message is produced.
On a second attempt I tried to redirect to the same page, redefining the get_success_url method to do nothing:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pass #return the appropriate success url
This returns a 404 error trying to redirect the browser to /reqs/1/None.
A third attempt to redirect to the form with the same record:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("update", kwargs={"pk": pk})
Which complains about not finding the view:
Reverse for 'update' not found. 'update' is not a valid view function or pattern name.
How can I redirect success to a valid URL? It can either be the items list or the item update view, as long as it works.
There are few misconception that you did
reverse parameter should be as documented
URL pattern name or the callable view object
You have set namespace but you are not reversing with namespace as documented
So in your case
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("reqs:update", kwargs={"pk": pk})
reverse / reverse_lazy are used to get the url using view name or pattern name. If you want to use a url directly just write:
success_url = '/'
For the case of return reverse("update", kwargs={"pk": pk}) not working since you set app_name = 'reqs' you should be using return reverse("reqs:update", kwargs={"pk": pk}) instead.

Page not found (404) Request Method: POST Request URL:http://127.0.0.1:8000/reg_done

from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('', views.Index, name='Index'),
path('register/', views.register, name='register'),
path('login/', views.login, name='login'),
path('register/reg_done/', views.reg_done,),
]
Above is my urls.py. What I am trying is to get my reg_done page come up as after a user click on the submit button to save the regestration info. But its showing that this page not found.
I tried to change the path in form action as register/reg_done/. But then it showed the same error with register/register/reg_done.
In HTML I am giving form action the value"/reg_done", that's it.
Below is my views.py
from django.shortcuts import render
from django.http import HttpResponse
import sqlite3
# Create your views here.
def Index(request):
return render(request, 'index.html')
def register(request):
return render(request, 'register.html')
def login(request):
return render(request, 'login.html')
def reg_done(request):
name = request.POST.get('name')
mail = request.POST.get('mail')
phone = request.POST.get('phone')
psw = request.POST.get('psw')
pswr = request.POST.get('pswr')
all = [name, mail, phone, psw, pswr]
return render(request, 'reg_done.html', {'all':all})
I assume the register view handles the registration. In the form for the register.html file,
Do:
<form action="/register/reg_done" method="post">
your form fields here
</form>
if you want to go with pure HTML solution. If you want something more Django-ly, use the url template tag:
<form action="{% url 'reg_done' %}" method="post">
your form fields here
</form>
I'll advice the later to ensure you avoid "premium developer tears".
In views.py, add this to the top of the file:
from django.views.decorators.http import require_POST
Then edit the reg_done view to:
#require_POST
def reg_done(request):
name = request.POST.get('name')
mail = request.POST.get('mail')
phone = request.POST.get('phone')
psw = request.POST.get('psw')
pswr = request.POST.get('pswr')
all = [name, mail, phone, psw, pswr]
return render(request, 'reg_done.html', {'all':all})
Next, change the path for reg_done in urls.py to:
path('register/reg_done/', views.reg_done, name='reg_done'),
The problem lies in your usage of urls. You should reference the reg_done view as /register/reg_done instead of register/reg_done. The former treats it as an url relative to the domain name while the latter treats it as relative to the current page. That's why coming from the register view and going to the latter yields register/register/reg_done rather than what you want: register/reg_done.

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/

Django DetailView occurs an error?

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.

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)),