Are declaring variables in dispatch method valid in following sample code?
if not is there better coding standards in CBV? my idea is avoiding redeclare the same variable in every http methods
class MyClsName(View):
template_name = "setup/code_install.html"
def dispatch(self, request, *args, **kwargs):
self.name = request.session['name']
self.obj = MyModel.objects.get(Name=self.name)
return super(MyClsName, self).dispatch(request, *args, **kwargs)
def get(self, request):
# obj = MyModel.objects.get(Name=self.name) --> to avoid
context = {'account': self.obj}
return render(request, self.template_name, context)
def post(self, request):
# obj = MyModel.objects.get(Name=self.name) --> to avoid
context = {'account': self.obj}
return render(request, self.template_name, context)
This is not invalid, but it is definitely not taking advantage of the features of class-based views. It is rarely necessary to overwrite either of dispatch or get/post. Instead, you should choose a more appropriate view to subclass, and override the specific methods it provides. In this case, since you want to display a single object, the appropriate base class is DetailView, and the method to override is get_object.
class MyClsName(DetailView):
template_name = "setup/code_install.html"
def get_object(self, queryset=None):
return MyModel.objects.get(Name=self.request.session['name'])
Note no need to define dispatch, get, or post at all.
Related
I created a view that manages the products which belong to the current log-in user (so the current user can watch its own products):
class ProductList(View):
def get(self, request, *args, **kwargs):
products = Product.objects.get(user=self.request.user)
#...
I would like to subclass another view from it, which manages the products that belong to a user specified as a GET parameter in the url (probably different to the current user). Is it possible to do it with super() and overriding the method in product_list? Something like this:
class ProductListFromUser(ProductList):
def get(self, request, *args, **kwargs):
#not sure what code if necessary to put here...
super().get(self, request, *args, **kwargs):
#or here
#...
Basically, how can I override the get method from product_list , changing only "self.request.user" by the parameter taken from the URL
You shouldn't do either of these.
If you have a view that renders a list of items, you should use a subclass of ListView. In that view, you can define the product list in the get_queryset method, which you can define in the relevant way in your two different views.
class ProductList(ListView):
def get_queryset(self, *args, **kwargs):
return Product.objects.filter(user=self.request.user)
class ProductListFromUser(ListView):
def get_queryset(self, *args, **kwargs):
return Product.objects.filter(user__username=self.request.GET['user'])
You should rarely need to define get (or post) directly in a class-based view.
I would move the retrieval of the appropriate QuerySet to an extra method which you can override in order to reuse as much of the initial code as possible:
class ProductList(View):
def get_products(self):
return Product.objects.all()
def get(self, request, *args, **kwargs):
products = self.get_products()
# ...
class ProductList(View):
def get_products(self):
qs = super().get_products()
qs = qs.filter(user=self.request.user)
return qs
# no get needed
This very functionality comes with django's ListView and its get_queryset method.
If you retrieve the queryset for a given request using the get_queryset method, that CBV such as DetailView use, then you can simply override the get_queryset of the parent-class.
I am attempting to override the update method for a put request in django rest framework. Instead of returning just the updated object. I want it to return the entire queryset including the updated object. For the use case I am working on, its just easier.
Starting from the top.
I am using Django Rest Frameworks Generics.
class SearchCityDetail(RetrieveUpdateDestroyAPIView):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
I override the classes PUT method and inherent from my custom mixin.
class SearchCityDetail(RetrieveUpdateDestroyAPIView, UpdateReturnAll):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
def put(self, request, *args, **kwargs):
return self.updatereturnall(self,request, *args, **kwargs)
the custom mixin looks like this (my custom added code which differs from the normal update code had the commment #Custom Code above it:
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.mixins import UpdateModelMixin
"""
Update a model instance and return all.
"""
#Custom Code
class UpdateReturnAll(UpdateModelMixin):
#custom name normally just update
def updatereturnall(self, request, model, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
#all objects and all serializer is custom code
allobjects = self.get_queryset(self)
allserializer = self.get_serializer(allobjects, many=True)
return Response(allserializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
The self.get_queryset and the self.get_serializer are functions defined in the GenericAPIView which RetrieveUpdateDestroyAPIView inherents from. And since I am inheriting the UpdateReturnAll into the SearchCityDetail class the two methods should be available to the UpdateReturnALL
that is my understanding.
I am currently getting an error and status code 500
the error being: AttributeError: 'SearchCityDetail' object has no attribute 'data'
what am I doing wrong?
It should be:
class SearchCityDetail(RetrieveUpdateDestroyAPIView, UpdateReturnAll):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
def put(self, request, *args, **kwargs):
return self.updatereturnall(request, *args, **kwargs)
Instead of return self.updatereturnall(self,request, *args, **kwargs).
Don't need to pass self argument explicit when call self.updatereturnall method, Python do it for you.
I am not very comfortable using class-based views but I am aware of their perks so I am forcing myself to start using them more often.
There's this view that receives a path param: manage/:id to manage a particular entity.
class MyView(TemplateView):
template_name = '...'
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['entity'] = get_object_or_404(Entity, pk=self.args[0])
return context
An Entity includes a list of authorized users to perform special actions. This view, MyView is one of those special actions.
I tried making a decorator for the view but it required finding the Entity first so I wasn't sure how to work that out.
Now, I have a check_permission(request, entity) function that checks if the current user is one of these authorized ones.
My question is where should I call this function in the class-based views like MyView which will be any of these views considered "special actions"?
Should I call it just from get_context_data()?
put it into dispatch(). It could look like this:
class MyView(TemplateView):
template_name = '...'
def dispatch(self, request, *args, **kwargs):
entity = get_object_or_404(Entity, pk=args[0])
if not check_permission(request, entity):
raise Http404
return super(MyView, self).dispatch(request, *args, **kwargs)
You can check permissions in the dispatch as yedpodtrzitko has said. I think it's also a good idea to throw it inside of a mixin that you can put on your views.
Here's an example:
from django.core.exceptions import PermissionDenied
class ViewPermissionsMixin(object):
"""Base class for all custom permission mixins to inherit from"""
def has_permissions(self):
return True
def dispatch(self, request, *args, **kwargs):
if not self.has_permissions():
raise PermissionDenied
return super(ViewPermissionsMixin, self).dispatch(
request, *args, **kwargs)
class MyCustomPermissionMixin(ViewPermissionsMixin):
def has_permissions(self):
# here you will have access to both
# self.get_object() and self.request.user
return self.request.user in self.get_object().special_list_of_people
Now you can throw MyCustomPermissionMixin on your view:
class MyView(MyCustomPermissionMixin, TemplateView):
# ...
In your case, since you're using a TemplateView, you should also make a get_object() method that returns the object that you want to deal with. Template views don't have this method by default.
Finally, just want to say that you will love Django's class based views once you learn some more about how to use them.
Take a look at Django Braces, it's a solid set of mixins which are designed around permissions.
How specifically you deal with permissions depends largely on implementation. I've done it in dispatch() before which is the way Braces does it but if it's specific to an object or queryset, I'll do it in the actual get_object or get_queryset methods as part of a DetailView.
For example if you had a creator associated with an Entity, you could override get_object to check the current logged in user is the Entity's creator.
class EntityView(LoginRequiredMixin, DetailView):
model = Thing
def get_object(self, **kwargs):
return Entity.objects.get_object_or_404(
pk=kwargs['entity_id'],
creator=self.request.user
)
Note: LoginRequiredMixin is a part of Braces. Very slick.
I have the following code:
class MyView(View):
var2 = Choices.objects.get(id=1)
my_strings = ['0','1','2','3']
#login_required
def myfunction(self,request):
return render(request,
'app/submit.html',{'my_strings':my_strings, 'var2':var2})
I want to access "var2" and "my_string" variables and display them in the template submit.html. If I use only the function without putting it in a class, everything works fine. But inside the class it shows errors.
Can anybody tell me how to access "var2" and "my_string" class variables in "myfunction" ?
You have to use self. In front of class variables.
Your function names in class based views should correspond to what http method you try to use(get, post etc...)
#login_required
def get(self,request):
return render(request,
'app/submit.html',{'my_strings':self.my_strings, 'var2':self.var2})
Please also read:
https://docs.djangoproject.com/en/1.9/topics/class-based-views/intro/
You don't have to write custom function to dispatch request...Django internally have the GET and POST method to do that... And also preferred way to handle login required is method_decorator
from django.utils.decorators import method_decorator
#method_decorator(login_required, name='dispatch')
class MyView(View):
string = "your string"
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
def get(self, request):
return render(request, 'template', {'string': self.string})
In DRF, I have a simple ViewSet like this one:
class MyViewSet(viewsets.ViewSet):
def update(self, request):
# do things...
return Response(status=status.HTTP_200_OK)
When I try a PUT request, I get an error like method PUT not allowed. If I use def put(self, request): all things work fine. Accordingly to the docs I should use def update(): not def put():, why does it happen?
PUT needs id in URL by default
Sometimes there is the difference between POST and PUT, because PUT needs id in URL
That's why you get the error: "PUT is not Allowed".
Example:
POST: /api/users/
PUT: /api/users/1/
Hope it'll save a lot of time for somebody
Had a similar "Method PUT not allowed" issue with this code, because 'id' was missing in the request:
class ProfileStep2Serializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
class Step2ViewSet(viewsets.ModelViewSet):
serializer_class = ProfileStep2Serializer
def get_queryset(self):
return Profile.objects.filter(pk=self.request.user.profile.id)
Turned out that i have missed 'id' in the serializer fields, so PUT request was NOT able to provide an id for the record. The fixed version of the serializer is below:
class ProfileStep2Serializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
This answer is right, Django REST framework: method PUT not allowed in ViewSet with def update(), PUT is not allowed, because DRF expects the instance id to be in the URL. That being said, using this mixin in your ViewSet is probably the best way to fix it (from https://gist.github.com/tomchristie/a2ace4577eff2c603b1b copy pasted below)
class AllowPUTAsCreateMixin(object):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
This is because the APIView has no handler defined for .put() method so the incoming request could not be mapped to a handler method on the view, thereby raising an exception.
(Note: viewsets.ViewSet inherit from ViewSetMixin and APIView)
The dispatch() method in the APIView checks if a method handler is defined for the request method.If the dispatch() method finds a handler for the request method, it returns the appropriate response. Otherwise, it raises an exception MethodNotAllowed.
As per the source code of dispatch() method in the APIView class:
def dispatch(self, request, *args, **kwargs):
...
...
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
# here handler is fetched for the request method
# `http_method_not_allowed` handler is assigned if no handler was found
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs) # handler is called here
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
Since .put() method handler is not defined in your view, DRF calls the fallback handler .http_method_not_allowed. This raises an MethodNotAllowed exception.
The source code for .http_method_not_allowed() is:
def http_method_not_allowed(self, request, *args, **kwargs):
"""
If `request.method` does not correspond to a handler method,
determine what kind of exception to raise.
"""
raise exceptions.MethodNotAllowed(request.method) # raise an exception
Why it worked when you defined .put() in your view?
When you defined def put(self, request): in your view, DRF could map the incoming request method to a handler method on the view. This led to appropriate response being returned without an exception being raised.
def update(self, request, pk=None):
data_in = request.data
print(data_in)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=False)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
data_out = serializer.data
return Response(serializer.data)
Using Django viewsets you can easily map the method in the url e.g.
path('createtoken/', CreateTokenView.as_view({'post': 'create', 'put':'update'}))
Then in your class override the methods as you please:
class CreateTokenView(viewsets.ModelViewSet):
queryset = yourSet.objects.all()
serializer_class = yourSerializer
def create(self, request, *args, **kwargs):
#any method you want here
return Response("response")
def update(self, request, *args, **kwargs):
# any method you want here
return Response("response")
I have multiple objects that are working with ModelViewSet and all have different (unique) fields for lookup.
So I came up with another solution for this question by defining put on a parent class and a new field lookup_body_field that can be used to associate payload with existing object:
class CustomViewSet(viewsets.ModelViewSet):
lookup_body_field = 'id'
def put(self, pk=None):
lookup_value = self.request.data.get(self.lookup_body_field)
if not lookup_value:
raise ValidationError({self.lookup_body_field: "This field is mandatory"})
obj = self.get_queryset().filter(**{self.lookup_body_field: lookup_value}).last()
if not obj:
return self.create(request=self.request)
else:
self.kwargs['pk'] = obj.pk
return self.update(request=self.request)
class MyViewSetA(CustomViewSet)
model = ModelA
lookup_body_field = 'field_a' # Unique field on ModelA
class MyViewSetB(CustomViewSet)
model = ModelB
lookup_body_field = 'field_b' # Unique field on ModelA
Suppose
you have registered a route like this (in urls.py)
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user-viewset')
urlpatterns += router.urls
and your restapi routs starts with /api/.
ViewSets generates follow routs.
create a user (send user object in body)
POST
/api/users/
get list of users
GET
/api/users/
get a user
GET
/api/users/{id}/
update a user (full object update)
PUT
/api/users/{id}/
partial update for a user
PATCH
/api/users/{id}/
delete a user
DELETE
/api/users/{id}/