Define renderer class dynamically in Django view function - python

How can I define the renderer class inside the Djanog old api_view function depending on some condition? To have something like this:
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
renderer_classes = [PDFRenderer]
else:
renderer_classes = [JSONRenderer]

From Django docs you can use different responses objects:
from django.http import FileResponse, JsonResponse
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
response = FileResponse(open('myfile.png', 'rb'))
else:
response = JsonResponse({'foo': 'bar'}, encoder=myJsonEncoder)

You can set the attributes accepted_renderer and accepted_media_type on the request yourself if the renderer_classes list you want to set contains only one renderer class. This will cause the correct renderer to be used:
#api_view(['GET'])
def can_render_2_things(request):
if some_comdition:
request.accepted_renderer = PDFRenderer
request.accepted_media_type = PDFRenderer.media_type
else:
request.accepted_renderer = JSONRenderer
request.accepted_media_type = JSONRenderer.media_type
But this is not very useful if you want to set more renders, I would advice you to simply move to using class based views instead of function based ones and use the APIView:
from rest_framework.views import APIView
class CanRender2Things(APIView):
def get(self, request, format=None):
if some_comdition:
self.renderer_classes = [PDFRenderer]
else:
self.renderer_classes = [JSONRenderer]

Related

how to get id of current object

how to get current id instead of static id (7)
to add image to flat
'''
class CreateFlat(CreateAPIView):
serializer_class = CreateFlat
queryset = Flat.objects.all()
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
print(request.data)
my_img = request.data['id_image']
ima = Images.objects.get(id=my_img)
print(self.id)
print(self)
flat = Flat.objects.get(id=7)
flat.images.add(ima);
serializer = FlatSerializer(flat, many=True)
return Response("done")
'''
There are so many errors but I think I can make you clear
First of all I would like to tell you about your view in general:
If you use CreateAPIView it using CreateModelMixin you should work with create method, not post, but it does not really matter ok.
In CreateAPIView there is only post method so you can remove queryset attribute
Keep your view class thin and add your logic in serializer, create method in serializer in this case
And finally about your question. if your path url looks like this flat/<int:pk>/add-images then try this
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
id = self.kwargs[lookup_url_kwarg]

Why I get an error when adding __init__(self) method to Django rest framework viewset class?

I Am keep getting a error when trying to build a Django API.
I have this class:
from uuid import UUID
from django.shortcuts import render
from django.http.response import JsonResponse
from django.http.request import HttpRequest
from rest_framework import viewsets, status
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from Instruments import serializers
from Instruments.services import InstrumentsService
from rest_framework.decorators import api_view
from Instruments.services import InstrumentsService
from Instruments.models import Instrument
from Instruments.serializers import InstrumentsSerializer
# Application views live here
class InstrumentViewSet(viewsets.ViewSet):
# instruments = Instrument.objects.all()
def __init__(self):
# self.instrument_service = InstrumentsService()
# self.instruments = Instrument.objects.all()
super().__init__()
def list(self, request: HttpRequest):
try:
self.instruments = Instrument.objects.all()
serializer = InstrumentsSerializer(self.instruments, many=True)
# data = self.instrument_service.get_instruments()
data = serializer.data
return JsonResponse(data, status=status.HTTP_200_OK, safe=False)
except Exception as exc:
return JsonResponse(
{"Status": f"Error: {exc}"},
status=status.HTTP_400_BAD_REQUEST,
safe=False,
)
when the init() method is defining even if it is just doing pass the django server gives me this error when I send a request:
TypeError at /api/
__init__() got an unexpected keyword argument 'suffix'
If I remove or comment out the init() method it works.why??
The “got an unexpected keyword argument” exception is rather descriptive. What it means is your class instance (in this case, your ViewSet instance) was initialized with a keyword argument that you’re not handling. That can be fixed by doing the following in your init method:
def __init__(self, **kwargs):
# self.instrument_service = InstrumentsService()
# self.instruments = Instrument.objects.all()
super().__init__(**kwargs)
This is required because the super class (View) utilizes **kwargs when the instance is initialized.
For the record, this is not using Django as it was intended. Django was never meant for service layers and using the init method like this is counterproductive since a ViewSet takes a queryset variable. I would encourage you to read the documentation much more thoroughly before continuing with this project of yours.

Using custom class view in django get me error

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()

Django OAuthToolkit Scopes per specific method

I'm using Django Rest Framework and OAuthTookit.
I want that the scope provided by the token should be HTTP Method specific. For eg:- GET, PUT, DELETE of the same APIView should have different scopes.
Following are my APIs.
class MyView(RetrieveUpdateDestroyAPIView):
permission_classes = [TokenHasScope]
required_scopes = ['scope1']
serializer_class = ModelSerializer
queryset = Model.objects.all()
Currently, the scope is set at the class level, which means to access all the GET, PUT & DELETE method, the token should have scope1.
I want that there should be different scope for different HTTP methods. How can I set different scope for different methods?
To handle this case, I think you need to implement a new permission class, something like this:
class TokenHasScopeForMethod(TokenHasScope):
def has_permission(self, request, view):
token = request.auth
if not token:
return False
if hasattr(token, "scope"):
# Get the scopes required for the current method from the view
required_scopes = view.required_scopes_per_method[request.method]
return token.is_valid(required_scopes)
And use it in your view like this:
class MyView(RetrieveUpdateDestroyAPIView):
permission_classes = [TokenHasScopeForMethod]
required_scopes_per_method = {'POST': ['post_scope'], 'GET': ['get_scope']}
serializer_class = ModelSerializer
queryset = Model.objects.all()
Perhaps You can use TokenMatchesOASRequirements permission class
class SongView(views.APIView):
authentication_classes = [OAuth2Authentication]
permission_classes = [TokenMatchesOASRequirements]
required_alternate_scopes = {
"GET": [["read"]],
"POST": [["create"], ["post", "widget"]],
"PUT": [["update"], ["put", "widget"]],
"DELETE": [["delete"], ["scope2", "scope3"]],
}
I like #clément-denoix answer, but would tweak it a bit.
Instead of reloading TokenHasScope.has_permission I would suggest to redefine
TokenHasScope.get_scopes as it is smaller method and perfectly fit for what you need. Also original has_permission method has additional logic that I prefer to preserve.
Something like this should do the trick:
from django.core.exceptions import ImproperlyConfigured
from oauth2_provider.contrib.rest_framework import TokenHasScope
class TokenHasScopeForMethod(TokenHasScope):
def get_scopes(self, request, view):
try:
scopes = getattr(view, "required_scopes_per_method")
return scopes[request.method]
except (AttributeError, KeyError):
raise ImproperlyConfigured(
"TokenHasScope requires the view to define the required_scopes_per_method attribute"
)

Accessing the user's request in a post_save signal

I have done the below post_save signal in my project.
from django.db.models.signals import post_save
from django.contrib.auth.models import User
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
The operation_by column, I want to get the user_id and store it. Any idea how can do that?
Can't be done. The current user is only available via the request, which is not available when using purely model functionality. Access the user in the view somehow.
I was able to do it by inspecting the stack and looking for the view then looking at the local variables for the view to get the request. It feels like a bit of a hack, but it worked.
import inspect, os
#receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
for entry in reversed(inspect.stack()):
if os.path.dirname(__file__) + '/views.py' == entry[1]:
try:
user = entry[0].f_locals['request'].user
except:
user = None
break
if user:
# do stuff with the user variable
Ignacio is right. Django's model signals are intended to notify other system components about events associated with instances and their respected data, so I guess it's valid that you cannot, say, access request data from a model post_save signal, unless that request data was stored on or associated with the instance.
I guess there are lots of ways to handle it, ranging from worse to better, but I'd say this is a prime example for creating class-based/function-based generic views that will automatically handle this for you.
Have your views that inherit from CreateView, UpdateView or DeleteView additionally inherit from your AuditMixin class if they handle verbs that operate on models that need to be audited. The AuditMixin can then hook into the views that successfully create\update\delete objects and create an entry in the database.
Makes perfect sense, very clean, easily pluggable and gives birth to happy ponies. Flipside? You'll either have to be on the soon-to-be-released Django 1.3 release or you'll have to spend some time fiddlebending the function-based generic views and providing new ones for each auditing operation.
You can do that with the help of middleware. Create get_request.py in your app. Then
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def current_request():
return _requests.get(current_thread().ident, None)
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread().ident] = request
def process_response(self, request, response):
# when response is ready, request should be flushed
_requests.pop(current_thread().ident, None)
return response
def process_exception(self, request, exception):
# if an exception has happened, request should be flushed too
_requests.pop(current_thread().ident, None)
Then add this middleware to your settings:
MIDDLEWARE = [
....
'<your_app>.get_request.RequestMiddleware',
]
Then add import to your signals:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from <your_app>.get_request import current_request
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print(Current User, current_request().user)
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
Why not adding a middleware with something like this :
class RequestMiddleware(object):
thread_local = threading.local()
def process_request(self, request):
RequestMiddleware.thread_local.current_user = request.user
and later in your code (specially in a signal in that topic) :
thread_local = RequestMiddleware.thread_local
if hasattr(thread_local, 'current_user'):
user = thread_local.current_user
else:
user = None
For traceability add two attributes to your Model(created_by and updated_by), in "updated_by" save the last user who modified the record. Then in your signal you have the user:
models.py:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
created_by = models. (max_length=100)
updated_by = models. (max_length=100)
views.py
p = Question.objects.get(pk=1)
p.question_text = 'some new text'
p.updated_by = request.user
p.save()
signals.py
#receiver(pre_save, sender=Question)
def do_something(sender, instance, **kwargs):
try:
obj = Question.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass
else:
if not obj.user == instance.user: # Field has changed
# do something
print('change: user, old=%s new=%s' % (obj.user, instance.user))
You could also use django-reversion for this purpose, e.g.
from reversion.signals import post_revision_commit
import reversion
#receiver(post_save)
def post_revision_commit(sender, **kwargs):
if reversion.is_active():
print(reversion.get_user())
Read more on their API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api
You can do a small hack by overriding you model save() method and setting the user on the saved instance as additional parameter. To get the user I used get_current_authenticated_user() from django_currentuser.middleware.ThreadLocalUserMiddleware (see https://pypi.org/project/django-currentuser/).
In your models.py:
from django_currentuser.middleware import get_current_authenticated_user
class YourModel(models.Model):
...
...
def save(self, *args, **kwargs):
# Hack to pass the user to post save signal.
self.current_authenticated_user = get_current_authenticated_user()
super(YourModel, self).save(*args, **kwargs)
In your signals.py:
#receiver(post_save, sender=YourModel)
def your_model_saved(sender, instance, **kwargs):
user = getattr(instance, 'current_authenticated_user', None)
PS: Don't forget to add 'django_currentuser.middleware.ThreadLocalUserMiddleware' to your MIDDLEWARE_CLASSES.
I imagine you would have figured this out, but I had the same problem and I realised that all the instances I create had a reference to the user that creates them (which is what you are looking for)
it's possible i guess.
in models.py
class _M(models.Model):
user = models.ForeignKey(...)
in views.py
def _f(request):
_M.objects.create(user=request.user)
in signals.py
#receiver(post_save, sender=_M)
def _p(sender, instance, created, **kwargs):
user = instance.user
No ?
Request object can be obtained from frame record by inspecting.
import inspect
request = [
frame_record[0].f_locals["request"]
for frame_record in inspect.stack()
if frame_record[3] == "get_response"
][0]
def get_requested_user():
import inspect
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request = frame_record[0].f_locals['request']
return request.user
else:
return None
context_processors.py
from django.core.cache import cache
def global_variables(request):
cache.set('user', request.user)
----------------------------------
in you model
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.core.cache import cache
from news.models import News
#receiver(pre_delete, sender=News)
def news_delete(sender, instance, **kwargs):
user = cache.get('user')
in settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'web.context_processors.global_variables',
)

Categories