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
Related
new to coding so I'm sure this is a simple problem but I can't seem to figure it out. I've abbreviated the code so it's simpler to see the problem.
urls.py
router = routers.DefaultRouter()
router.register(r'clients', views.ClientViewSet, basename='client')
urlpatterns = [
#Bunch of other paths here.
path('client/<int:pk>/contacts',
login_required(views.contacts_by_client), name="client-contacts"),
path('api/', include(router.urls)),
]
views.py
def contacts_by_client(request, pk):
client = Client.objects.get(id=pk)
contact_list = Contact.objects.filter(user=request.user, client=pk)
context = {
'contacts': contact_list,
'client': client
}
return render(request, 'pages/contacts-client.html', context)
class ClientViewSet(viewsets.ModelViewSet):
serializer_class = ClientSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=True, methods=['get'], name="Contacts")
def contacts(self, request, pk=None):
# Bunch of code here.
My suspicion is that the router is creating a route name called "client-contacts" based on the action created in views.py, however, I don't understand why it would take precedence over the explicitly labeled url pattern that comes before it.
I know I must be missing something super simple, but I can't figure it out. Thank you all for your help!
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()
When I try to access http://localhost:8000/api/articles/ it works fine.
When I try to access http://localhost:8000/api/articles/1 it works fine.
When I try to access http://localhost:8000/api/articles/create Django thinks that I am trying to perform a GET request ('get': 'retrieve'). What am I doing wrong?
errors invalid literal for int() with base 10: 'create'
urls.py
urlpatterns = [
path('', ArticleViewSet.as_view({'get': 'list'}), name='list'),
path('<pk>/', ArticleViewSet.as_view({'get': 'retrieve'}), name='detail'),
path('create/', ArticleViewSet.as_view({'post': 'create'}) ,name='create'),
]
views.py
class ArticleViewSet(ViewSet):
queryset = Article.objects.all()
def list(self, request):
articles = query_filter(request, ArticleViewSet.queryset)
serializer = ArticleSerializer(articles, many=True)
articles = formatter(serializer.data)
return Response(articles)
def retrieve(self, request, pk=None):
article = get_object_or_404(ArticleViewSet.queryset, pk=pk)
serializer = ArticleSerializer(article, many=False)
article = formatter([serializer.data])
return Response(article)
def create(self, request):
articles = ArticleViewSet.queryset
articles.create(title=request.data['title'], body=request.data['body'])
article = articles.last()
serializer = ArticleSerializer(article, many=False)
article = formatter([serializer.data])
return Response(article)
Also when I switch the positions of retrieve and create in the urlpatterns shown below, I get this error "detail": "Method \"GET\" not allowed.".
urlpatterns = [
path('', ArticleViewSet.as_view({'get': 'list'}), name='list'),
path('create/', ArticleViewSet.as_view({'post': 'create'}), name='create'),
path('<pk>/', ArticleViewSet.as_view({'get': 'retrieve'}), name='detail'),
]
When you are trying with http://localhost:8000/api/articles/create, you are actually making GET request. That is why you are seeing the error("detail": "Method \"GET\" not allowed.".). If you want to make post request, then you need to use api tools like postman. If you use postman, try like this:
And your second url pattern is correct. because if you keep <pk>/ before create/, django interprets that you are calling <pk>/(should be <int:pk>)url with argument create(which is a string), when you are actually calling the create method. And when it tries to convert it to integer(as primary key is an autofield), it throws invalid literal for int() with base 10: 'create' exception.
Create method doesn't not support get action that's why you're getting error {"detail": "Method \"GET\" not allowed."}. Alternative you can try ModelViewset that provides default create(), retrieve(), update(), partial_update(), destroy() and list() actions.
Either you can create get_serailzer() method using that you can get browsable API with JSON and HTML form through that you can do POST action.
class ArticleViewSet(viewsets.ViewSet):
queryset = Article.objects.all()
def get_serializer(self, *args, **kwargs):
return ArticleSerializer(*args, **kwargs)
def create(self, request, *args, **kwargs):
articles = ArticleViewSet.queryset
articles.create(title=request.data['title'], body=request.data['body'])
article = articles.last()
**serializer = self.get_serializer(article, many=False)**
from pyreadline.logger import formatter
article = formatter([serializer.data])
return Response(article)
Browsable API Image
Solution: I was using slashes at the end and that when I was sending request I wasn't adding slashes at the end. So simply I deleted end slashes from django urls
I want to add a X-Frame-Options header to Django CreateView. I need this because I'll be serving a form that will be loaded in iframe tags.
The problem is, there are several methods in django class-based views that return HttpResponse objects.
Is there a way to add the header to the responses without overwriting all those methods?
class MyView(CreateView):
def get(self, request, *args, **kwargs):
resp = super(MyView, self).get(request, *args, **kwargs)
resp['X-Frame-Options'] = ''
return resp
# Same would go for form_invalid, post, put, etc...
Okay, I fixed it. If you've encountered similar problem, here's how to do it.
You have to overwrite render_to_response method in same way I did with get in the example code above.
I tried the overwrite render to response method, but I wanted a solution that I could use for a whole chunk of urls mapped to several views, and not have to deal with overwriting the same method on several views.
I made a middleware class based off of django-cors-headers so I could allow iframe-ing of part of my django app. I keep a middleware.py in my main project directory and save a couple random middleware classes I have made there, like this one here and a ForceResponse Exception for example.
import re
from django import http
from django.conf import settings
class XFrameAllowMiddleware(object):
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
if (self.is_enabled(request) and
request.method == 'OPTIONS' and
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META):
response = http.HttpResponse()
return response
return None
def process_response(self, request, response):
if self.is_enabled(request):
response['X-Frame-Options'] = 'ALLOWALL'
return response
def is_enabled(self, request):
return re.match(settings.XFRAME_URLS_REGEX, request.path)
Add it to your MIDDLEWARE_CLASSES and configure the regex in your settings:
MIDDLEWARE_CLASSES = (
...
'your_django_app.middleware.XFrameAllowMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
XFRAME_URLS_REGEX = r'^/iframe_this_url/.*$'
from the django-cors-headers read.me:
CORS_URLS_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under /api/.
Example:
CORS_URLS_REGEX = r'^/api/.*$'
Default:
CORS_URLS_REGEX = '^.*$'
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.