Ensuring django template is never cached by any browser - python

I have a django template with a class-based DetailView associated to it. I've over-ridden the get_context_data method of the DetailView, using it to pass some required context variables that I display in the template (essentially an image). That's all I've done.
How do I ensure this particular django template of mine is never cached by any browser? Wading through various sources tells me I need to over-ride the HttpResponse in the dispatch method to accomplish no-cache?
I understand that I'll need to set the Cache-Control, Pragma, Expiry etc. I've just been unable to make headway regarding whether to (or how to) over-ride the dispatch method. Can someone give me a simple clarificational example of how they would go about implementing no-cache for such a template?

Firstly, it's the view rather than the template that you want to control HTTP caching on.
The template is just a chunk of HTML that can be rendered by any view, the view is what sends an HTTP Response to the web browser.
Django comes with some handy view decorators for controlling the HTTP headers returned by a view:
https://docs.djangoproject.com/en/1.9/topics/cache/#controlling-cache-using-other-headers
You can find this simple example in the docs:
from django.views.decorators.cache import never_cache
#never_cache
def myview(request):
# ...
If you are using 'class-based' views rather than simple functions then there's a gist here with an example of how to convert this decorator into a view Mixin class:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
class NeverCacheMixin(object):
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
return super(NeverCacheMixin, self).dispatch(*args, **kwargs)
...which you'd use in your project like:
from django.views.generic.detail import DetailView
class ArticleView(NeverCacheMixin, DetailView):
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"

Related

Control requests to view and template output in django

This is a view for get all the records in the EducationalRecord model:
def all_education_resume(request):
RESUME_INFO['view'] = 'education'
educations_resume = EducationalRecord.objects.all().order_by('-created_date')
template = 'resumes/all_resume.html'
context = {'educations_resume': educations_resume, 'resume_info': RESUME_INFO}
return render(request, template, context)
Now, if I want to write exactly this view for other models (like job resumes, research resumes , etc.),
I must another view one separately.
My question is:
How can I get a view for all these requests, so first check the URL of
the request and then do the relevant query? How can I control URL
requests in my views?
My other question is exactly the same as my first question,with this difference:
control view that must render in specific template.In other words,in
second question the ratio between the template and the view is instead
of the ratio of the view to the url or how to create a template for
multiple views (for example, for a variety of database resume
resumes, I have a template) and then, depending on which view render,
the template output is different.
I have implemented these two issues as follows:
I wrote a view for each of request!
In each view, I set the value of RESUME_INFO['view'], and then I've checked it in a template page and specified the corresponding template.
What is the best solution to these two questions?
How can I get a view for all these requests, so first check the URL of the request and then do the relevant query? How can I control URL requests in my views?
You can access request.path, or you can let the url(..)s pass a parameter with kwargs that holds a reference to the model for example, but this is usually bad design. Typically if you use different models, you will likely have to order these different as well, filter these differently, render these differently, etc. If not, then this typically indicates that something is wrong with the modeling.
You can however make use of class-based views [Django-doc], to remove as much boilerplate as posssible. Your view looks like a ListView [Django-doc], by using such view, and patching where necessary, we can omit most of the "boilerplate" code:
# app/views.py
from django.views.generic.list import ListView
class MyBaseListView(ListView):
resume_info = None
template = 'resumes/all_resume.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['resume_info'] = {'view': self.resume_info}
return context
In the individual listviews, you then only need to specify the resume_info and the model or queryset to render it with the 'all_resume.html' template, for example:
# app/views.py
# ...
class EducationalResumeView(MyBaseListView):
queryset = EducationalRecord.objects.order_by('-created_date')
resume_info = 'education'
class OtherModelView(MyBaseListView):
model = OtherModel
resume_info = 'other_info'
So we can here use inheritance to define common things only once, and use it in multiple views. In case we need to change something in a specific view, we can override it at that level.
In the urls.py, you define such view with the .as_view() method [Django-doc]. For example:
# app/urls.py
from django.urls import path
from app.views import EducationalResumeView, OtherModelView
urlpatterns = [
path('education/', EducationalResumeView.as_view()),
path('other/', OtherModelView.as_view()),
]

Convert GET parameters to POST data on a Request object in Django REST Framework

I am in the process of rewriting the backend of an internal website from PHP to Django (using REST framework).
Both versions (PHP and Django) need to be deployed concurrently for a while, and we have a set of software tools that interact with the legacy website through a simple AJAX API. All requests are done with the GET method.
My approach so far to make requests work on both sites was to make a simple adapter app, routed to 'http://<site-name>/ajax.php' to simulate the call to the Ajax controller. Said app contains one simple function based view which retrieves data from the incoming request to determine which corresponding Django view to call on the incoming request (basically what the Ajax controller does on the PHP version).
It does work, but I encountered a problem. One of my API actions was a simple entry creation in a DB table. So I defined my DRF viewset using some generic mixins:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
# ...
This adds a create action routed to POST requests on the page. Exactly what I need. Except my incoming requests are using GET method... I could write my own create action and make it accept GET requests, but in the long run, our tools will adapt to the Django API and the adapter app will no longer be needed so I would rather have "clean" view sets and models. It makes more sense to use POST for such an action.
In my adapter app view, I naively tried this:
request.method = "POST"
request.POST = request.GET
Before handing the request to the create view. As expected it did not work and I got a CSRF authentication failure message, although my adapter app view has a #csrf_exempt decorator...
I know I might be trying to fit triangle in squares here, but is there a way to make this work without rewriting my own create action ?
You can define a custom create method in your ViewSet, without overriding the original one, by utilizing the #action decorator that can accept GET requests and do the creation:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
#action(methods=['get'], detail=False, url_path='create-from-get')
def create_from_get(self, request, *args, **kwargs):
# Do your object creation here.
You will need a Router in your urls to connect the action automatically to your urls (A SimpleRouter will most likely do).
In your urls.py:
router = SimpleRouter()
router.register('something', MyViewSet, base_name='something')
urlpatterns = [
...
path('my_api/', include(router.urls)),
...
]
Now you have an action that can create a model instance from a GET request (you need to add the logic that does that creation though) and you can access it with the following url:
your_domain/my_api/something/create-from-get
When you don't need this endpoint anymore, simply delete this part of the code and the action seizes to exist (or you can keep it for legacy reasons, that is up to you)!
With the advice from all answers pointing to creating another view, this is what I ended up doing. Inside adapter/views.py:
from rest_framework.settings import api_settings
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.response import Response
from rest_framework import status
from mycoreapp.renderers import MyJSONRenderer
from myapp.views import MyViewSet
#api_view(http_method_names=["GET"])
#renderer_classes((MyJSONRenderer,))
def create_entity_from_get(request, *args, **kwargs):
"""This view exists for compatibility with the old API only.
Use 'POST' method directly to create a new entity."""
query_params_copy = request.query_params.copy()
# This is just some adjustments to make the legacy request params work with the serializer
query_params_copy["foo"] = {"name": request.query_params.get("foo", None)}
query_params_copy["bar"] = {"name": request.query_params.get("bar", None)}
serializer = MyViewSet.serializer_class(data=query_params_copy)
serializer.is_valid(raise_exception=True)
serializer.save()
try:
headers = {'Location': str(serializer.data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
headers = {}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Of course I have obfuscated the names of everything specific to my project. Basically I reproduced almost exactly (except for a few tweaks to my query params) what happens in the create, perform_create and get_success_header methods of the DRF mixin CreateModelMixin in a single function based DRF view. Being just a standalone function it can sit in my adapter app views so that all legacy API code is sitting in one place only, which was my intent with this question.
You can write a method for your viewset (custom_get) which will be called when a GET call is made to your url, and call your create method from there.
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
def custom_get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
And in your urls.py, for your viewset, you can define that this method needs to be called on a GET call.
#urls.py
urlpatterns = [
...
url(r'^your-url/$', MyViewSet.as_view({'get': 'custom_get'}), name='url-name'),
]
As per REST architectural principles request method GET is only intended to retrieve the information. So, we should not perform a create operation with request method GET. To perform the create operation use request method POST.
Temporary Fix to your question
from rest_framework import generics, status
class CreateAPIView(generics.CreateView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.query_params)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
def get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Please refer below references for more information.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
https://learnbatta.com/blog/introduction-to-restful-apis-72/

How to use Django's DetailView (or some built-in view class) for a home page?

I'm building an app in Django and I would like to set up a home page for authenticated users.
I thought it was best to use de built-in generic.DetailView for this, given that the details I want to show in the page are those of the identified user. The thing is, this class needs to receive the id of the target entity through the URL as a decimal pk argument. However, the information about the entity, is in request.user, and I would like to avoid repeating that information so as not to show my user ids in the urls.
I would imagine that getting the request within the get_object method would do the trick, but it does not take arguments.
Is this possible? Is this a good idea? Are there any alternatives that I may be missing?
The DetailView only needs the pk or slug to fetch the object. If you override get_object then it is not required. In the get_object method, you can access the user with self.request.user.
Finally, if your view uses the logged in user, then you can use the LoginRequiredMixin to ensure that only logged in users can access the view.
from django.contrib.auth.mixins import LoginRequiredMixin
class MyDetailView(LoginRequiredMixin, DetailView):
...
def get_object(self):
return self.request.user

Admin site uses default database in Django 1.6

I have a project with several apps, each having its own database. I have a working routers.py to specify the correct database to use for each model. One of the app uses the django-admin subsite to manage its models.
This was all working fine in Django 1.5, but when moving to Django 1.6, I am not anymore able to edit my models: When I access the edit page of a model, I am getting an error "[SQL Server Native Client 11.0]Login timeout expired".
After some investigation, it seems like Django tries to connect to the database default which contains only dummy data in this project, as it should not be used anywhere.
Why does Django try to connect to the default database in 1.6, whereas it was working fine in 1.5? How can I fix this issue in 1.6?
[EDIT]
After some more research, I have found that Django uses the decorator #transaction.atomic for a few functions of its class ModelAdmin.
Looking at the code of this decorator, it seems like it's intended to support the possibility of specifying the database alias that needs to be used for this atomic transaction, with #transaction.atomic(using), and otherwise, if just called with #transaction.atomic as it is in the ModelAdmin Django class, it will use the DEFAULT_DB_ALIAS defined in django.db.utils.
I have tried unsuccessfully to override this behaviour and make these functions use the database alias I want.
Would you know of any way to do this?
I have finally found a way to force Django admin to use the database I want, but it's really hacky. If someone comes with a better solution, please do post it.
I have created a file named admin_helper.py where I redefine the methods that use the #transaction.atomic decorator in Django codebase, with exactly the same content, and using the decorator #transaction.atomic(DATABASE_ALIAS) which tells Django to use the correct database:
DATABASE_ALIAS = 'alias_of_the_database_i_want_to_use'
class ModelAdminCustom(admin.ModelAdmin):
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def add_view(self, request, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def change_view(self, request, object_id, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def delete_view(self, request, object_id, extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
class MultiDBUserAdmin(UserAdmin):
#sensitive_post_parameters_m
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def add_view(self, request, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.auth.admin.UserAdmin
# Unregister existing instance of UserAdmin for the User model, and register my own
try:
admin.site.unregister(User)
finally:
admin.site.register(User, MultiDBUserAdmin)
Then in my normal admin.py:
from myapp.admin_helper import ModelAdminCustom
class MyModelAdmin(ModelAdminCustom):
pass
admin.site.register(MyModel, MyModelAdmin)

Login Required Decorator with Many Possible HTML's for Same URL

I would like to restrict access to urls that are served by django generic views. I have researched the login required decorator, but have only had partial success in getting it to work as I have a complication that is not addressed in the docs (or at least I couldn't find it).
Before adding the decorator, in urls.py I have the following:
url(r'^search/(?P<search_type>\w+)', search)
The above named search function is slightly complex in that depending on various conditions, it will render one of four possible html pages.
I don't see in the docs a way to handle multiple html pages using the decorator, and I can't seem to figure out the correct syntax.
I have tried using the decorator with one of the four html pages, and it does work for that one html page:
from django.views.generic import TemplateView
url(r'^search/(?P<search_type>\w+)',
login_required(TemplateView.as_view(template_name='search_form.html',), search)),
But how do I require login for all the possible html's? For example, I tried things like:
url(r'^search/(?P<search_type>\w+)',
login_required(TemplateView.as_view(template_name='search_form.html',), TemplateView.as_view(template_name='search_form_manual.html',), search)),
I also tried subclassing the generic views:
//in view.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name_1 = "search_form.html"
template_name_2 = "search_form_manual.html"
template_name_3 = "search_results.html"
template_name_4 = "tag_search_results.html"
//in urls.py
from views import AboutView
url(r'^search/(?P<search_type>\w+)',
login_required(AboutView.as_view(template_name_1, template_name_2,), search)),
But I get errors that template_name_1 and template_name_2 do not exist...
Any help is appreciated.
Use that with class view
from django.views.generic import TemplateView
class AboutView(TemplateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
template_name_1 = "search_form.html"
template_name_2 = "search_form_manual.html"
template_name_3 = "search_results.html"
template_name_4 = "tag_search_results.html"

Categories

Resources