In Django, when using class based views, it is commonplace to setup class-level variables such as template_name.
class MyView(View):
template_name = 'index.html'
def get(self, request):
...
I am wondering if modifying these variables during runtime will persist across multiple requests or just the current one.
class MyView(View):
template_name = 'index.html'
def get(self, request):
if only_returns_true_once_function():
self.template_name = 'something.html'
...
Each request creates a new instance of that class, handles the request, and destroys it. The reason for class-based views is not to maintain instances, it's to allow inheritance and mixin composition. This makes it substantially easier to create reusable functionality that spans multiple views.
You can change variables at any point in the class' lifetime. The only point that these variables become important is when the request is handled, specifically during the dispatch() method, which other HTTP action methods like get() and post() wrap.
I strongly encourage you to bookmark the Classy Class-based Views site because it offers an incredibly thorough overview of how class-based views are composed and how they inherit. The most appropriate way to change the template names in a class based view is to override the get_template_names() method on a TemplateView.
class MyView(TemplateView):
def get_template_names(self):
if some_contrived_nonce_function():
return 'something.html'
else:
return super(MyView, self).get_template_names()
The above assumes your view either inherits from TemplateView or implements TemplateResponseMixin.
Modifying this as:
self.template_name = 'something.html'
will definitely only last for that request.
Modifying it as:
type(self).template_name = 'something.html'
will cause new instances to inherit your changes.
Related
I'm following along with a Django Rest Framework tutorial (source code here) and I have a few questions about the below code snippet:
class ReviewCreate(generics.CreateAPIView):
serializer_class = ReviewSerializer
permission_classes = [IsAuthenticated]
throttle_classes = [ReviewCreateThrottle]
def get_queryset(self):
return Review.objects.all()
def perform_create(self, serializer):
pk = self.kwargs.get('pk')
watchlist = WatchList.objects.get(pk=pk)
review_user = self.request.user
review_queryset = Review.objects.filter(watchlist=watchlist, review_user=review_user)
if review_queryset.exists():
raise ValidationError("You have already reviewed this movie!")
if watchlist.number_rating == 0:
watchlist.avg_rating = serializer.validated_data['rating']
else:
watchlist.avg_rating = (watchlist.avg_rating + serializer.validated_data['rating'])/2
watchlist.number_rating = watchlist.number_rating + 1
watchlist.save()
serializer.save(watchlist=watchlist, review_user=review_user)
In the class definition, the variable serializer_class is declared; however in the perform_create method, serializer is an argument. Given the differences in naming, how are these two related?
In the method perform_create, self.kwargs is referenced. However, I don't see a kwargs argument passed to any __init__ method or else attached to the class object. How/where is kwargs passed to the class?
In both cases, I can only assume that the inherited class (generics.CreateAPIView) has an __init__ method that assigns a serializer_class variable to serializer. How it "listens" for a child class definition of serializer_class, I have no idea. And as for kwargs, I'm at a loss for how this is passed to the child class w/o explicitly calling defining it in its arguments.
Edit, this question Kwargs in Django does not answer my question-- it just explains what keyword arguments are. I'm not confused about their name, I'm confused by their invisible yet implicit reference in this code.
Answering your first point, we have to note two things:
First, the method perform_create is used in the create method associated to CreateModelMixin (see https://github.com/encode/django-rest-framework/blob/71e6c30034a1dd35a39ca74f86c371713e762c79/rest_framework/mixins.py#L16). The class CreateAPIView inherits from this mixin and also from GenericAPIView(See https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L184). As you can see, the create method mentioned above uses the class perform_create method and needs a serializer there. Defining perform_create without that argument would lead to an error when creating objects with this method.
Another thing to note is that the serializer used comes from the get_serializer method. Checking the source code for GenericAPIView (https://github.com/encode/django-rest-framework/blob/b1004a47334a0dd1929e6d50b8f7ff6badc959f4/rest_framework/generics.py#L103) we can see that this method calls get_serializer_class which retrieves the serializer defined by serializer_class.
In conclusion, if you don't modify anything else, the serializer that will be passed as a parameter will be an instance of you serializer class defined in serializer_class.
Getting to your second point, if you try to search the parent class of GenericAPIView and follow on searching the base class from which these classes inherit, you will end up finding that the base class is View from django.views.generic. There you will find in the setup method (https://github.com/django/django/blob/27aa7035f57f0db30b6632e4274e18b430906799/django/views/generic/base.py#L124) where the kwargs attribute is initialized. Also you can see in this method's code documentation the following statement:
"""Initialize attributes shared by all view methods."""
Thus in any view we create (if it has View as its base class) we will always be able to manipulate self.request, self.args and self.kwargs. I hope I explained myself clearly!
I re-use an existing drf model viewset but there are some custom actions (assigned with #action label) that i don't need. How can I hide/remove it from django rest framework without modifying the origional model viewset?
for example
class MyViewSet(viewsets.ModelViewSet):
#action(["get",], detail=False)
def custom_a(self, request):
# some stuff
#action(["get",], detail=False)
def custom_b(self, request):
# some stuff
#action(["get",], detail=False)
def custom_c(self, request):
# some stuff
My router
router = routers.SimpleRouter()
router.register("dummies", views.MyViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Then I will have these endpoints
GET /dummies/
GET /dummies/{id}/
POST /dummies/
PUT /dummies/{id}/
PATCH /dummies/{id}/
DELETE /dummies/{id}/
GET /dummies/custom_a/
GET /dummies/custom_b/
GET /dummies/custom_c/
Now how can I just keep 5 first views and GET /dummies/custom_a/?
Thanks.
There are a few ways to do this, but the "cleanest" seems overriding.
Override & Ignore
Decorators are not inherited, so you can just re-declare the method on your derived class. This new method takes precedence over the base class method, so DRF no longer sees them as #actions.
class View1(viewsets.ModelViewSet):
#action(['get'], detail=False)
def act_up(self, request):
pass
class Meta:
model = Client
fields = "__all__"
class View2(View1):
# redefine and don't add the #action decorator
def act_up(self, request):
pass
class Meta:
model = View1.Meta.model
fields = View1.Meta.fields
Use a router and just removed the methods you don't want
Router URLs are calculated once and then cached. You could make this happen, then filter out the ones you don't want (by name)
router = SimpleRouter()
router.register("view1", View1, basename="v1")
router._urls = [
r for r in router.urls
if not any(r.name.endswith(bad) for bad in ['-act-up', '-other-rt']
]
Manually route to the actions
You can manually create the routes you need, in the same manner that the base SimpleRouter does. This is a lot more work, and most certainly not worth it
list_paths = View1.as_view({"get": "list"})
detail_paths = View1.as_view({"get": "retrieve", "patch": "partial_update"})
urlpatterns = [
path("view1/", list_paths, name="view1-list"),
path("view1/<int:pk>/", detail_paths, name="view1-detail")
]
What this does is bind a url + http method (e.g. get) to a specific DRF "action" (list/retrieve/destroy,etc). So GET view11/ calls your viewset with action=list, which the viewset then dispatches to the correct method internally.
Since you only map the things you want, there is no way for those bad "other" methods to be called.
It is a bit trickier, and confusing, and makes you responsible for the routing which, together, make it more work to maintain & understand. If there is no other way, or if you only want 1 or 2 methods from the ViewSet, then its an OK option.
Let's say I want to use the LoginRequiredMixin and a UserPermissionMixin created by myself and apply them to all the views in an app. This is just an example, I might also have mixins that add some context or do other stuff.
I could do it manually, for example this view:
class MyCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView)
But, since I have many views and I might have other specific mixins for some views, this gets messy and hard to manage.
One solution that came to mind would be to create new classes for the generic views:
class DecoratedCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView):
pass
class DecoratedDetailView(LoginRequiredMixin, UserPermissionMixin, DetailView):
pass
class DecoratedUpdateView(LoginRequiredMixin, UserPermissionMixin, UpdateView):
pass
class DecoratedDeleteView(LoginRequiredMixin, UserPermissionMixin, DeleteView):
pass
and then, use these as my generic views:
class MyCreateView(DecoratedCreateView)
Is this a good approach? Do I have to add any methods in the classes above or do I just leave them blank and it'll work as expected?
Is there any other way to achieve this, maybe in urls.py ?
Your approach is good. I've been doing so for some projects with a slight difference:
myapp/views/generic.py
from django.views.generic import (
CreateView as BaseCreateView,
DetailView as BaseDetailView,
UpdateView as BaseUpdateView,
DeleteView as BaseDeleteView,
)
__all__ = ['MyappMixin', 'CreateView', 'DetailView', 'UpdateView', 'DeleteView']
class MyappMixin(LoginRequiredMixin, UserpermissionMixin):
pass
class CreateView(MyappMixin, BaseCreateView):
pass
class DetailView(MyappMixin, BaseDetailView):
pass
class UpdateView(MyappMixin, BaseUpdateView):
pass
class DeleteView(MyappMixin, BaseDeleteView):
pass
myapp/views/base.py
from .generic import CreateView
class MyCreateView(CreateView):
pass
It works fine, without much hassle, and allows you to easily skip the mixin exceptionally if needed.
According to the usecase, another solution might be to use middlewares or context processors.
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
is_in_myapp = request.resolver_match.app_name == 'myapp'
if is_in_myapp and not request.user.is_authenticated:
response = HttpResponse("Permission denied", status=403)
else:
response = self.get_response(request)
return response
I will have standard class-based views for CRUD operations that inherit from various generic views like ListView, DetailView and so on.
I will be setting all of their
context_object_name
attribute to the same value.
I was wondering if there is a way to do it more pythonic, to not repeat the operations many times in the code, but to be able to change that variable in one place if necessary?
ps. what comes to my mind is of course further inheritance, but maybe there is some more django-like way?
You can also use a mixin, instead of a middleware app:
class CommonContextMixin(object):
def get_context_data(self, *args, **kwargs):
context = super(CommonContextMixin, self).get_context_data(*args, **kwargs)
context['foo'] = 'bar'
return context
Then use that mixin in your views:
class MyView(TemplateView, CommonContextMixin):
""" This view now has the foo variable as part of its context. """
Relevant Django docs: https://docs.djangoproject.com/en/2.1/topics/class-based-views/mixins/
Middleware can do the trick
class SetContextObjectNameMiddleware:
def process_template_response(self, request, response):
if 'object' in response.context_data:
response.context_data['foo'] = response.context_data['object']
return response
Then add the middleware to your settings.py
It's not really setting the view's context_object_name but it achieves the same outcome.
I have been working on this all day.
I am trying to write custom permission for class views to check if user is in a certain group of permissions.
def rights_needed(reguest):
if request.user.groups.filter(Q(name='Admin')).exists():
pass
else:
return HttpResponseRedirect('/account/log-in/')
#method_decorator(rights_needed, name='dispatch')
class AdminView(CreateView):
model = Admin
form_class = AdminForm
def get_template_names(self):
return 'clinic/visitform_list.html'
Could help me know how I can achieve this? Or an easier way around it?
I also tried this (code inside AdminView class):
def dispatch(self, request):
if request.user.groups.filter(Q(name='Admin')).exists():
return super().dispatch(*args, **kwargs)
else:
return HttpResponseRedirect('/account/log-in/')
A decorator is a function that takes a function (a view in this case), and returns another function (a view in this case). At the moment your rights_needed looks like a regular view - it’s returning a response not a function.
Django comes with a user_passes_test method that makes it easy to create decorators like this. Since you are using class based views, it would be even easier to use the UserPassesTest mixin.
Your test function for the mixin would be:
def test_func(self):
return self.request.user.groups.filter(Q(name='Admin')).exists()