Using custom class view in django get me error - python

I'm learning django and i'm trying to create my own custom class in a views.py file
This class i would use have to method, one for classical HTML rendering, and another for json response
my class in views.py
class myListView():
context = {}
def __init__(self, request):
request = request
context['PageTitle'] = 'Contacts'
context['people'] = People.objects.all()
def htmlRender(self, *args, **kwargs):
context['children_template'] = 'people/list.html'
return render(request,'base.html',context)
def jsonRender(self, *args, **kwargs):
return HttpResponse(json.dumps(self.context['people']), content_type="application/json")
my urls.py
path('list', login_required(myListView.htmlRender()), name='list'),
path('list/json', login_required(myListView.jsonRender()), name='list'),
Here is the error sended by debugger :
TypeError: htmlRender() missing 1 required positional argument: 'self'
I don't have any idea how to solve this, maybe i'm dreaming about using custom class in view ?
Thanks'you

from django.views.generic import ListView
class myListView(ListView):
Maybe you are not extending the ListView Class, try this out.

You should first create an instance from "myListView" class then use it :
myListViewInstance = myListView(arguments)
myListViewInstance.htmlRender()

Related

AttributeError 'UserViewset' object has no attribute 'action'

I've been using the Django rest framework, and I'm trying to customize the get_parsers method in my UserViewset, I looked at the docs and found a similar use case with permission classes in docs, and I tried to customize the get_parsers like this
class UserViewset(viewsets.ModelViewSet):
serializer_class = UserSerializer
# Redefine get_parsers, so that only the update methods have form-data media type
def get_parsers(self):
if self.action == 'update' or self.action == 'partial_update':
parser_classes = [FormParser, MultiPartParser]
else:
parser_classes = [JSONParser]
return [parser() for parser in parser_classes]
but I keep getting the error: AttributeError at /api/auth/users/: 'UserViewset' object has no attribute 'action'
I tried the use case found in the docs and it worked perfectly.
What am I doing wrong here ?
So, thanks to fb.com/laidani.basset #bdbd and #willem-van-onsem and u/vikingvynotking, I've been able to fix it with 2 different solutions:
The first is to override initialize_request like this, the idea is to set the request parsers to the instance of which parser you want; the drop back is when you're using swagger or any API doc, it'll not differentiate between the method parsers and it'll be just one parser, in my case it's JSONParser:
def initialize_request(self, request, *args, **kwargs):
request = super().initialize_request(request, *args, **kwargs)
print(request.method)
if request.method in ['PUT', 'PATCH']:
request.parsers = [FormParser(), MultiPartParser()]
else:
request.parsers = [JSONParser()]
self.action = self.action_map.get(request.method.lower())
return request
The second solution is to override initialize_request so that the self.action is called before calling the request, and then use the get_parsers method as you like, in this case, the swagger will differentiate between the parsers of each method:
def initialize_request(self, request, *args, **kwargs):
self.action = self.action_map.get(request.method.lower())
return super().initialize_request(request, *args, **kwargs)
You should cast your viewset to .as_view() for example;
path('users/', UserViewset.as_view(), name='users'),

How to re-use GET call of another class-based api view

I have a hard time trying to re-use a get call from an existing APIView in another APIVIew.
I have a class-based DRF view:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SomethingList.as_view()), #legacy url
# in views.py
class SomethingList(generics.ListCreateAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
# override get, because of some required custom action
def get(self, request, *args, **kwargs):
# do some custom actions (scan folder on filesystem)
...
return super().get(request, *args, **kwargs)
The above view both provides a get (list) and post (create) API interface. As intended. I've augmented it with DRF-spectacular information (not shown here) to generate my swagger docs.
Now, I have another (legacy) URL defined that should do exactly the same as the get (list) call above. Currently, this legacy url also points to the SomethingList.
But ... the legacy URL should NOT provide the post (create) interface, and I want to mark it as 'deprecated' in swagger using drf-spectacular. So I figured I need a separate class to restrict to get() and add the #extend_schema decorator
So I though of re-using the existing SomethingList.get functionality as follows:
# in urls.py
path('api/something', views.SomethingList.as_view()),
path('api/similarsomething', views.SimilarSomethingList.as_view()), # ! points to new class
# in views.py
class SomethingList(generics.ListCreateAPIView):
...
class SimilarSomethingList(generics.ListAPIView): #ListAPIView only!
#extend_schema(summary="Deprecated and other info..")
def get(self, request, *args, **kwargs):
view = SomethingList.as_view()
return view.get(request, *args, **kwargs)
However, this doesn't work. I get AttributeError: 'function' object has no attribute 'get'
I tried a couple of variations, but couldn't get that working either.
Question:
How can I reuse the get() call from another APIView? Should be simple, so I'm likely overlooking something obvious.
Set http_method_names to the class view.
class SomethingList(generics.ListCreateAPIView):
http_method_names = ['get', 'head']
reference: https://stackoverflow.com/a/31451101/13022138

Using a controller to return different views in a django app

I want to return multiple views in my app according to the logic in a controller which is connected to the url resolver.
Here is some example code of the idea that I have .
class Project(Singleton):
TYPE1, TYPE2 , TYPE3 = (0,1,2)
def init(self,request, slug):
self.pengine = ProjectEngine()
self.pengine.init()
self.request = request
self.slug = slug
ptype = pengine.getProjectType()
return self.showProjectView(ptype)
def showProjectView(self, projectType):
if(projectType == TYPE1):
return Type1View.as_view(self.request, self.slug)
elif(projectType == TYPE2):
return Type2View.as_view(self.request, self.slug)
else:
return Type3.as_view(self.request, self.slug)
Type1View for example extends from Djangos default TemplateView class. ProjectEngine is supposed to be the model class to get the data from.
The url resolver would then get the callable view from the init method, either using the Singleton idea that I have here, or using a class only method the same way as how a base View class is implemented.
I'm just not sure if I should be using the MVC pattern this way. The url resolver is supposed to resolve directly to the view class methods, and I want to send that data from the controller , using the model class to the view. How do I achieve this?
Does the as_view method have a way to send data through to it? Or should I let go of Django's View class and make another View here that corresponds to the design.

How to support all REST operations for an endpoint in django rest framework

I have a subscription model that looks like this
class Subscription(models.Model):
name = models.CharField(max_length=100)
quantity = models.IntegerField(max_length=20)
stripe_id = models.CharField(max_length=100)
user = models.ForeignKey(User)
I would like to create an endpoint that allows POST, PATCH, DELETE, GET
So I did the following things
views.py
class SubscriptionDetail(viewsets.ModelViewSet):
serializer_class = SubscriptionSerializer
permission_classes = (IsAuthenticated,)
queryset = Subscription.objects.all()
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
def update(self, instance, validated_data):
print "In update"
#how do I write create and delete?
urls.py
subscription = SubscriptionDetail.as_view({
'patch': 'update'
})
url(r'^rest-auth/subscription/$', subscription, name='something'),
Questions
Using the above when I send a PATCH request, I get an error. How can I fix this?
Expected view SubscriptionDetail to be called with a URL keyword
argument named "pk". Fix your URL conf, or set the .lookup_field
attribute on the view correctly.
While sending the patch request I would also like to send an 'email' field which is not on the subscription model. Is this possible to do? I need the email field in the POST (create) operation so that I know which user the subscription belongs to.
The easiest way is to do it this way.
keep the models class the same
views.py
from rest_framework import viewsets
#impost serializer and model class for subscription
class SubscriptionViewSet(viewsets.ModelViewSet):
serializer_class = SubscriptionSerializer
def get_queryset(self):
queryset = Subscription.objects.all()
#if you need to get subscription by name
name = self.request.QUERY_PARAMS.get('name', None)
if name is not None:
queryset = queryset.filter(name=name)
return queryset
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
# django will handle get, delete,patch, update for you ....
# for customization you can use def update or def create ... to do whatever you need
# def create(self, validated_data):
# you can handle the email here
# and something like subscription= Subscription (name=validated_data['name'],vendor=validated_data['quantity']...)
# subscription.save()
# it will save whatever you want
urls.py
#use the router to handle everything for you
from django.conf.urls import patterns, include, url
from rest_framework import routers
#import your classes
router = routers.DefaultRouter()
router.register(r'subscription', views.SubscriptionViewSet,base_name='subscription')
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
For the creation of an Object you must implement the create function as described in the official documentation, found here. For patching you could use the partial argument from within you view class:
SubscriptionSerializer(subscription, data={'something': u'another', partial=True)
For deletion of the a Subscription, that could be done when you get the delete call as so in your view class:
if request.METHOD == 'DELETE':
subscription = Subscription.objects.get(pk=pk)
subscription.delete()
See this tutorial for complete example
Further more I think that you should include the "id" field in the SubscriptionSerialiser Meta class, otherwise it will be difficult to do the updates/deletions. I hope this helped a little.
Cheers,
Tobbe
When you want to use a method that allow make these operations you have to use a #detail_route() where you can say as well which methods will you use, like in the docs is said:
#detail_route(methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
...
So to be able to use them you should add the next decorator
#detail_route(methods=['post', 'patch'])
To add another parameters you can do it for the .save() parameter. You just have to indicate the name of this and them just override your .save() model to check if that email belongs or not to the user that is trying to do the subscription. Here I paste you what the Django Rest docs says:
" Passing additional attributes to .save()
...
You can do so by including additional keyword arguments when calling .save(). For example:
serializer.save(owner=request.user)
Here I leave you the link for more information:
http://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
Using the above when I send a PATCH request, I get an error. How can I fix this?
Expected view SubscriptionDetail to be called with a URL keyword
argument named "pk". Fix your URL conf, or set the .lookup_field
attribute on the view correctly.
The error is caused because unlike create request, patch/update require a pk to know which object to update. That is why you have to supply the pk value for it. So, your url for PUT, DELETE andPATCH must have at least named parameter like this -
subscription = SubscriptionDetail.as_view({
'patch': 'update'
})
url(r'^rest-auth/subscription/(?<pk>(\d+))$', subscription, name='something'),
an example url will be - rest-auth/subscription/10 where 10 is the pk or id of the object. Django Rest Framework will then load the object internally to be updated.
While sending the patch request I would also like to send an 'email' field which is not on the subscription model. Is this possible to do? I need the email field in the POST (create) operation so that I know which user the subscription belongs to.
To add custom parameters, first declare the property in serializer, it is better to keep it required=False, so that other request does not throw error -
class SubscriptionSerializer(serializers.ModelSerializer):
custom_field = serialiers.BooleanField(required=False)
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
def update(self, instance, validated_data):
print "In update"
so far this is enough for the django rest framework to accept the field custom_field and you will find the value in update method. To get the value pop it from the attributes supplied by the framework like this -
def update(self, instance, validated_data):
custom_field = validated_data.pop('custom_field', None)
if custom_field is not None:
# do whatever you like with the field
return super().update(instance, validated_data)
# for python < 3.0 super(SubscriptionSerializer, self).update(instance, validated_data)
When you overrided (I don't know if that's the proper conjugation of overriding a method) the update method, you stopped the ability to PUT or PATCH and object. Your new method only prints out "In update" but doesn't save the instance. Look at the update method from the serializer.ModelSerializer object:
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Notice the last few lines where the instance is saved with the values and then returned. Remove your update method on the SubscriptionSerializer object. This let's your parent object's create, update, retrieve, and delete methods do their magic which supports PATCH and PUT updates. The next problem is that your urls.py is using the Django rather than the REST framework router. Change it to this:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'subscription', SubscriptionDetail)
That should solve the patch update problem.
I don't think you can add an email field in your patch method without the attribute on the subscription model. That's just a guess on my part, and I may be wrong. Does the email field map to anything on any object? Can you use a ForeignKey to map it?
I hope that works for you, good luck!
In view.py you just need set the class with:
class SubscriptionDetail(mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
and add this to fix .lookup_field :
def update(self, request, *args, **kwargs):
log.error("OBJ update kwargs= %s , data = %s" % (kwargs, str(request.data)))
pk = request.data.get('id')
if (kwargs.get('pk') is not None):
kwargs['pk'] = request.data.get('id')
self.kwargs['pk'] = request.data.get('id')
return super().update(request, *args, **kwargs)
and add support to methods do you want :
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
# def get(self, request, *args, **kwargs):
# return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
# def patch(self, request, *args, **kwargs):
# return self.partial_update(request, *args, **kwargs)
#
# def delete(self, request, *args, **kwargs):
# return self.destroy(request, *args, **kwargs)
only tweak that remains is get for list or get for retrieve on element but should be easy now add something if we have one pk we may call self.retrieve else we may call self.list

How to access HttpRequest from urls.py in Django

Basically I want to use a generic view that lists objects based on a username. Now, the question is, how do I do something like:
(r'^resources/$',
ListView.as_view(
queryset=Resources.objects.filter(user=request.user.username),
...
)
)
I couldn't find a way to access the HttpRequest (request) object though... Or do I need to use my own views and do all object selection there?
You could try subclassing the generic view:
class PublisherListView(ListView):
def get_queryset(self):
return Resources.objects.filter(user=self.request.user.username)
Then your urls entry would look like:
(r'^resources/$',
PublisherListView.as_view(
...
)
)
More information on dynamic filtering in class based views can be found here: http://docs.djangoproject.com/en/dev/topics/class-based-views/#dynamic-filtering
If you really want to clutter your URLconf directly, you can do it like so:
(r'^resources/$',
lambda request: ListView.as_view(queryset=Resources.objects.filter(user=request.user.username), ...)(request)
)
Or access the request by subclassing the view:
class MyListView(ListView):
def dispatch(self, request, *args, **kwargs):
self.queryset = Resources.objects.filter(user = request.user.username)
return super(MyListView, self).dispatch(request, *args, **kwargs)

Categories