I am trying to change to class-based views in Django after an upgrade and I have two questions regarding this. This is my code, simplified:
# urls.py
urlpatterns += patterns('project.app.views',
('^$', 'index'), # Old style
url(r'^test/$', SearchView.as_view()), # New style
)
# views.py
class SearchView(TemplateView):
template_name = 'search.html'
def get_context_data(self, **kwargs):
messages.success(request, 'test')
return {'search_string': 'Test'}
When I run this I first get the error name 'SearchView' is not defined. Does anyone know why?
Trying to skip that I add from project.app.views import SearchView which is ugly and not the way I want it to work, but hey, I try to see if I can get the rest working. Then I get global name 'request' is not defined because of the messages. This makes sense but how do I get the request object here?
So I'd like to know: how do I get the views to work as intended and how to use messages in get_context_data()?
You are seeing name 'SearchView' is not defined because you have not imported SearchView into your urls.py. If you think this is ugly then you can do search = SearchView.as_view() in your views.py and use the old style to reference the view as search instead. The request can be accessed on self.request for adding the messages. The updated source example is given below.
# views.py
class SearchView(TemplateView):
template_name = 'search.html'
def get_context_data(self, **kwargs):
messages.success(self.request, 'test')
return {'search_string': 'Test'}
search = SearchView.as_view()
# urls.py
urlpatterns += patterns('project.app.views',
url('^$', 'index'), # Old style
url(r'^test/$', 'search'), # New style
)
Please ask a single question at a time (a StackOverflow guideline).
Anyway:
This is the way class-based views are intended to work. There's no auto-importing magic, you just import the class and use its as_view() method.
You can access the request object in your view class through self.request.
Related
Background
I have a ModelViewSet that defines several custom actions. I am using the default router in my urls.py to register the URLs. Right now, my views use the default created routes like ^images/$, ^images/{pk}/$.
In order for users to use the API using resource names with which they are familiar, I have adapted the viewset to accept multiple parameters from the URL, using the MultipleFieldLookupMixin strategy described in the docs to allow for the pattern images/{registry_host}/{repository_name}/{tag_name}.
I've created the get_object method in my viewset like so:
class ImageViewSet(viewsets.ModelViewSet):
...
def get_object(self):
special_lookup_kwargs = ['registry_host', 'repository_name', 'tag_name']
if all(arg in self.kwargs for arg in special_lookup_kwargs):
# detected the custom URL pattern; return the specified object
return Image.objects.from_special_lookup(**self.kwargs)
else: # must have received pk instead; delegate to superclass
return super().get_object()
I've also added a new URL path pattern for this:
urls.py
router = routers.DefaultRouter()
router.register(r'images', views.ImageViewSet)
# register other viewsets
...
urlpatterns = [
...,
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('', include(router.urls)),
]
Problem
The above all works as intended, however, I also have some extra actions in this model viewset:
#action(detail=True, methods=['GET'])
def bases(self, request, pk=None):
...
#action(detail=True, methods=['GET'])
def another_action(...):
... # and so on
With the default patterns registered by DRF, I could go to images/{pk}/<action> (like images/{pk}/bases) to trigger the extra action methods. However I cannot do this for images/{registry_host}/{repository_name}/{tag_name}/<action>. This is somewhat expected because I never registered any such URL and there's no reasonable way DRF could know about this.
I'm guessing that I can add all these paths manually with an appropriate arguments to path(...) but I'm not sure what that would be.
urlpatterns = [
...,
path('.../myaction', ???)
]
Questions
As a primary question, how do I add the actions for my URL?
However, I would like to avoid having to add new URLS every time a new #action() is added to the view. Ideally, I would like these to be registered automatically under the default path images/{pk}/<action> as well as images/{registry_host}/{repository_name}/{tag_name}/<action>.
As a secondary question, what is the appropriate way to achieve this automatically? My guess is perhaps a custom router. However, it's unclear to me how I would implement adding these additional routes for all extra (detail) actions.
Using django/drf 3.2
Another approach I see is to (ab)use url_path with kwargs like:
class ImageViewSet(viewsets.ModelViewSet):
#action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>/bases')
def bases_special_lookup(...):
# detail=False to remove <pk>
#action(detail=True, methods=['GET'])
def bases(...):
...
#action(detail=False, methods=['GET'], url_path='<str:registry_host>/<path:repository_name>)/<str:tag_name>')
def retrieve_special_lookup(...):
# detail=False to remove <pk>
def retrieve(...):
...
This will create these urls:
images/<str:registry_host>/<path:repository_name>/<str:tag_name>/bases
images/<pk>/bases
images/<str:registry_host>/<path:repository_name>/<str:tag_name>
images/<pk>
Theres probably a better way of doing this, but you could try something like this:
router = routers.DefaultRouter()
router.register(r'images', views.ImageViewSet)
# register other viewsets
...
urlpatterns = [
...,
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('images/<str:registry_host>/<path:repository_name>/<str:tag_name>/<str:action>', views.ImageViewSet.as_view({'get': 'retrieve',})),
path('', include(router.urls)),
]
and then in your viewset:
class ImageViewSet(viewsets.ModelViewSet):
...
def get_object(self):
special_lookup_kwargs = ['registry_host', 'repository_name', 'tag_name']
if all(arg in self.kwargs for arg in special_lookup_kwargs):
action = getattr(self, self.kwargs.pop('action', ''), None)
if action:
#not sure what your action is returning so you may not want to return this
return action(self.request)
# detected the custom URL pattern; return the specified object
return Image.objects.from_special_lookup(**self.kwargs)
else: # must have received pk instead; delegate to superclass
return super().get_object()
I'm new to Django and I've been trying to make so small app after reading the tutorial but I can't figure out what's wrong with my code.
What I'm trying to do is listing all the database entries of a model called project using a ListView with a form below it (using the same view) that the user can use to filter the entries to be shown by means of submitting data in text fields.
I managed to make that work, and it looks like this:
However, once the user clicks the "Filter results" button providing some filtering pattern on the textfields (let's say, filter by name = "PROJECT3", leaving the other fields blank), instead of rendering a page with the filtered data and the form below it, which is my intention, it just returns a white page.
Can anyone please explain me what is wrong with my code?
Here are the relevant parts:
forms.py
class FilterForm(forms.Form):
pjt_name = forms.CharField(label='Name', max_length=200, widget=forms.TextInput(attrs={'size':'20'}))
pjt_status = forms.CharField(label='Status', max_length=20, widget=forms.TextInput(attrs={'size':'20'}) )
pjt_priority = forms.CharField(label='Priority', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_internal_sponsor = forms.CharField(label='Int Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
pjt_external_sponsor = forms.CharField(label='Ext Sponsor', max_length=20, widget=forms.TextInput(attrs={'size':'20'}))
views.py
from App.models import Project
from django.views.generic import ListView
from django.shortcuts import render
from django.template import RequestContext
from App.forms import FilterForm
class ProjectListView(ListView):
context_object_name = 'project_list'
template_name='App/index.html'
def get_context_data(self, **kwargs):
context = super(ProjectListView, self).get_context_data(**kwargs)
if 'filter_form' not in context:
context['filter_form'] = FilterForm()
return context
def get_queryset(self):
form = FilterForm(self.request.GET)
if form.is_valid():
name = form.cleaned_data['pjt_name']
i_sp = form.cleaned_data['pjt_internal_sponsor']
e_sp = form.cleaned_data['pjt_external_sponsor']
status = form.cleaned_data['pjt_status']
pri = form.cleaned_data['pjt_priority']
return send_filtered_results(name, i_sp, e_sp, status, pri)
else:
return Project.objects.order_by('-project_creation_date')[:5]
def send_filtered_results(name, i_sp, e_sp, status, pri):
return Project.objects.filter(project_name__in=name,internal_sponsor_name__in=i_sp, external_sponsor_name__in=e_sp, project_status__in=status, project_priority__in=pri).exclude(alias__isnull=True).exclude(alias__exact='')
urls.py
from django.conf.urls import patterns, url
from App.views import ProjectListView
from django.views.generic import DetailView
from App.models import Project, Task
urlpatterns = patterns('',
url(r'^$',
ProjectListView.as_view())
The answer is in your response/status code:
After doing that I get a white page and runserver returns [23/Jan/2015 00:21:09] "POST /App/ HTTP/1.1" 405 0
You're POSTing to a view that has no POST handler. You should be getting an error saying so, but the 405 means method not allowed.
Add a post method to your CBV. Django class based views map request method to functions, so a GET is handled via CBV.get, POST via CBV.post
For demonstration purposes, add:
# essentially, mirror get behavior exactly on POST
def post(self, *args, **kwargs):
return self.get(*args, **kwargs)
And change your form handler to pull from request.POST not request.GET.
form = FilterForm(self.request.POST)
I suggest you start using print()s or log to start seeing what's happening. request.GET should be empty, as.. you're not using GET parameters. That data will be in request.POST.
Your code is messy and your Project.objects.filter(...) is far to aggressive. It just doesn't return any objects.
Don't use name__in=name but name__contains=name.
I've developed my own, but it seems like it's a great enough thing that someone else probably thought of it first and did a better job ;)
The goal is to be able to write, in your myapp/views.py
router = Router(prefix=r'^myapp/')
#router.route(url=r'my/url/here', name="my-great-view")
def myview(request):
return render_to_response("mytemplate.html", {})
And then in urls.py
urlpatterns += myapp.views.router.get_patterns()
And there are several other cool decorator things I've built in (#jsonview for taking a returned dictionary and making a json response, #use_template for taking a returned dictionary and passing it to the defined template as the context...)
It seems like this way makes everything a lot more localized an readable. When looking at a view function you don't have to search around to find what url it belongs to, or what it's "named".
I saw this one djangosnippet, but it's a lot more magic than I'd like, and it doesn't look like it's been generally adopted.
In the event that no one has put together a standard solution, should I clean mine up and submit a pull request to contrib?
here is what I currently have implemented: magic.py
Edit:
if you want multiple urls for the same view:
#router.route(url="my-first-url", kwargs={'foo':'bar'})
#router.route(url="my-second=url", kwargs={'foo':'baz'})
def my_view(...): ...
And of course this doesn't have to be the only way to do it -- the normal urlpatterns way has some nice things about it two, but these two methods are not mutually exclusive.
The regular configuration we have a main website urls.py . And the urls.py contains variable named urlpatterns.
so wo can push some url_pattern into it.
app/views.py
from django.urls import path as djpath
URLS = []
URLS_d = {}
def route(path=''):
def wrapper(func):
path_name = path or func.__name__
URLS.append(
djpath(path_name, func)
)
### below is not important ####
path_func = URLS_d.get(path_name, None)
if path_func:
print(path_func, '<>', func)
raise Exception('THE same path')
URLS_d[path_name] = func
### above is not important ####
return wrapper
#route()
def index(req):
return HttpResponse('hello')
website/urls.py
from app.views import URLS
urlpatterns.extend(URLS)
If you use class base view , you can use django-request-mapping,
A Simple Example
view.py
from django_request_mapping import request_mapping
#request_mapping("/user")
class UserView(View):
#request_mapping("/login/", method="post")
def login(self, request, *args, **kwargs):
return HttpResponse("ok")
#request_mapping("/signup/", method="post")
def register(self, request, *args, **kwargs):
return HttpResponse("ok")
#request_mapping("/<int:user_id>/role/")
def get_role(self, request, user_id):
return HttpResponse("ok")
#request_mapping("/<int:pk/", method='delete')
def delete(self, request, pk):
User.objects.filter(pk=pk).delete()
return HttpResponse("ok")
#request_mapping("/role")
class RoleView(View):
# ...
urls.py
from django_request_mapping import UrlPattern
urlpatterns = UrlPattern()
urlpatterns.register(UserView)
urlpatterns.register(RoleView)
and request urls are:
post: http://localhost:8000/user/login/
post: http://localhost:8000/user/signup/
get: http://localhost:8000/user/1/role/
delete: http://localhost:8000/user/1/
# ...
I am using django generic views, how do I get access to the request in my template.
URLs:
file_objects = {
'queryset' : File.objects.filter(is_good=True),
}
urlpatterns = patterns('',
(r'^files/', 'django.views.generic.list_detail.object_list', dict(file_objects, template_name='files.html')),
)
After some more searching, while waiting on people to reply to this. I found:
You need to add this to your settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
)
This means that by default the request will be passed to all templates!
None of the answers given solved my issue, so for those others who stumbled upon this wanting access to the request object within a generic view template you can do something like this in your urls.py:
from django.views.generic import ListView
class ReqListView(ListView):
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
c = super(ReqListView, self).get_context_data(**kwargs)
# add the request to the context
c.update({ 'request': self.request })
return c
url(r'^yourpage/$',
ReqListView.as_view(
# your options
)
)
Cheers!
Try using the get_queryset method.
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
see link (hope it helps):-
See Greg Aker's page...
What works for me was to add:
TEMPLATE_CONTEXT_PROCESSORS = ("django.contrib.auth.context_processors.auth",
"django.core.context_processors.request",
)
To the the settings.py not to the urls.py
How do I wrap a Django Form Wizard in a view? I need to do this so I can access request.
Does anyone have some example code for this?
I probably should be just commenting on Manoj's answer, but sounds you need code
urls.py
from django.conf.urls.defaults import *
from MyApp import views
urlpatterns = patterns(
'',
(r'^wizard/$', views.MyWizardView ),
)
views.py
#login_required
def MyWizardView (request):
cw = MyWizard([WizardName, WizardQuestions, WizardProvider, WizardGoomber])
return cw(request)
The as_view function converts a class based view into a callable view:
from django import forms
from django.contrib.formtools.wizard.views import SessionWizardView
class Form1(forms.Form):
a = forms.CharField()
class Form2(forms.Form):
b = forms.CharField()
class MyWizard(SessionWizardView):
pass
wizard_view = MyWizard.as_view([Form1, Form2])
def view(request):
# do something fancy with the request object here
return wizard_view(request)
This is basicly the same answer as in How to wrap a Django Form Wizard in a View?
This Django snippet may prove useful.
From the title: "FormWizard inside view with proper context handling and site templating support, without having to use urls.py"