How to catch a request in a DRF viewset? - python

So I have this:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
entity_name = 'user'
perm_type = {
'POST': 'create',
'GET': 'read',
'PATCH': 'update',
'DELETE': 'delete'
}
def check_permissions(self, request):
user = request.user
has_permissions = user.has_entity_permissions(
name=self.entity_name,
perm_type=self.perm_type[request.method]
)
if not has_permissions:
raise PermissionDenied
def create(self, request, *args, **kwargs):
self.check_permissions(request)
return super().create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
self.check_permissions(request)
return super().list(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
self.check_permissions(request)
return super().update(request, *args, **kwargs)
I have a custom security server, the purpose is to centralize all the apps of the company, so when we have a new employee, we can give him access to the differents apps with different permissions in every entity and their properties from a single app instead of creating the user and give him permissions in every app.
So basically in the "check_permission" function I check for this, depending in the request method (perm_type associated a request method with a permission (CRUD))
The question:
there is a way to catch the request before enters into list, retrive, create, update or delete (Middlewears dont work because i need to know the entity type or endpoint, thats why I set the entity_name variable, but if you have a better idea is welcome)

Why overriding your action method when you already have check_permissions implemented by APIView class? (which ModelViewSet inherits from)
Simply add your peace of code by overriding it.

Related

How to return the data from django signal?

I am using Django-rest for developing the API. In my case, when the user posts the data, I have to process the posted data (It will take 2-3 min). I wrote the Django signal for preprocessing data. My signal.py file looks like this,
#receiver(post_save, sender=ExposureIndex)
def calculate_exposure(instance, created, *args, **kwargs):
ear_table = instance.ear_index.name
haz_dir = instance.hazard_index.file.path
# calling celery task
task = exposure_calculation.delay(ear_table,haz_dir)
return task.id
And my celery calculation function is here,
#shared_task(bind=True)
def exposure_calculation(self, ear_table, haz_dir):
progress_recorder = ProgressRecorder(self)
CalculateExposure(ear_table, haz_dir)
return 'Done'
My django-rest view function is looks like this,
class ExposureIndexViewSet(viewsets.ModelViewSet):
queryset = ExposureIndex.objects.all()
serializer_class = ExposureIndexSerializer
permission_classes = [permissions.IsAuthenticated]
My question is when the user posts the data, I want to return the task.id instead of returning the actual response (I tried to return it from the Django signal but it is not returning in the actual API). Can anyone suggest to me how to return task.id instantly when the user posts the exposureIndexData?
I think you should override the create method in views.py rather than creating the signal instance. Do something like this in views.py file
class ExposureIndexViewSet(viewsets.ModelViewSet):
queryset = ExposureIndex.objects.all()
serializer_class = ExposureIndexSerializer
permission_classes = [permissions.IsAuthenticated]
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
instance = response.data
ear_table = instance['ear_table']
haz_dir = instance['haz_dir']
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
task = exposure_calculation.delay(ear_table,haz_dir)
return Response({'task_id': task.id})
instead of using signals u can simply send response through your view
def post(self,request):
#create database manaully
task=exposure_calculation.delay(ear_table,haz_dir)
return Response({"message",task.id})

How to remove put from a pre written view in django

I have the following view:
class ReadClass(generics.RetrieveUpdateDestroyAPIView):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
''' defined my post here'''
I know retrieveupdatedestroyapiview doesn't have post in it. And I have created my own post in the view here and on the front end, I see both post and put! Is there any way to remove the put.
Or is there any other way to do it better, I tried using ListCreateApi view. The problem with that is while it gives me the post functionality, it lists all the values, while I am looking for a specific pk. I cannot see any other generic view that gives me get and post functionality.
EDIT
I have added the edit as requested, try and except might seem unnecessary here at the moment, but I will add more functionality later on.
class ReadClass(generics.GenericAPIView, mixins.CreateModelMixin, mixins.RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
try:
s1 = MySerializer.objects.get(mRID=kwargs["pk"])
serializer = MySerializer(s1, data=request.data)
except MySerializer.DoesNotExist:
pass
if serializer.is_valid():
if flag == 0:
pass
else:
serializer.update(s1,validated_data=request.data)
else:
return Response(serializer.errors)
urlpatterns = [path('temp/<int:pk>', ReadClass.as_view(), name = " reading"),]
DRF has mixins for List, Create, Retrieve, Update and Delete functionality. Generic views just combine these mixins. You can choose any subset of these mixins for your specific needs. In your case, you can write your view like this, if you only want Create and Retrieve functionalty:
class ReadClass(GenericAPIView, CreateModelMixin, RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
This would provide default functionality for post and get requests. If you prefer, you can override post method like you did in your example to customize post requset behavior.
You can read more about mixins and generic views here

Where/how to replace default upload handler in Django CBV?

I am trying to specify a specific method of handling file uploads for a class based view. Per the docs this can be achieved by something like:
from django.core.files.uploadhandler import TemporaryFileUploadHandler
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
If i specify this in post method of a FormView like so:
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return super().post(self, request, *args, **kwargs)
I get:
AttributeError: You cannot set the upload handlers after the upload has been processed.
Variants like yield the same result:
def post(self, request, *args, **kwargs):
self.request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
However when i do this in the get method this is ineffective:
def get(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
return super().get(self, request, *args, **kwargs)
If I upload a small file it still uses the default django.core.files.uploadhandler.MemoryFileUploadHandler.
What am I doing wrong?
EDIT
Also when i try to mirror what is suggested in the note, I get the same AttributeError:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#csrf_exempt
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request, *args, **kwargs)
#csrf_protect
def _post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Ok, finally got it to work (using the suggestions provided by #Alasdair). Setting a method decorator(crsf_exempt) on post is not engough it needs to be on dispatch. For anyone struggling with this in the future, it goes like this:
from django.views.generic import FormView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#method_decorator(csrf_exempt, 'dispatch')
class UploadDataSetView(FormView):
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request)
#method_decorator(csrf_protect)
def _post(self, request):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Also it will fail if you remove the {% csrf_token %} from your template (which is what you want).
Because you cannot change upload handler in view as it is something that gets invoked prior to your view function.
Get shouldn't collect post parameters so it behaves accordingly.
Upload Handlers
When a user uploads a file, Django passes off the file data to an
upload handler – a small class that handles file data as it gets
uploaded. Upload handlers are initially defined in the
FILE_UPLOAD_HANDLERS setting, which defaults to:
["django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler"]
If you want different upload handler you can change FILE_UPLOAD_HANDLERS settings
If this is not enough you can write your custom upload handler
Edit:
Also, request.POST is accessed byCsrfViewMiddleware which is enabled
by default. This means you will need to use csrf_exempt() on your view
to allow you to change the upload handlers.

django-braces access mxin different login_url redirect per mixin

I am using djang-braces
I would like to use a different login_url for each mixin called on a view.
for instance I would like all LoginRequiredMixin calls to redirect to 'accounts/login' and all MultiplePermissionsRequiredixin calls to redirect to 'accounts/permissions'.
As the 2 mixins are subclassed from AccessMixin setting the 'login_url' on a view will affect both mixins, I cannot see how I can specify a redirect for each Mixin.
I currently have views like this:
class View(LoginRequiredMixin, MultiplePermissionsRequiredMixin, View):
permissions = {
"all": (<modelmethods>),
}
login_url = '/accounts/permissions'
def get(self, request, *args, **kwargs):
cool view stuff
def post(self, request, *args, **kwargs):
cool view stuff
but of course this redirects both mixins to '/accounts/permissions'
I am wanting to achieve something like:
class View(LoginRequiredMixin, MultiplePermissionsRequiredMixin, View):
permissions = {
"all": (<modelmethods>),
}
Login_Redirect = '/accounts/login'
MultiplePermissions_Redirect = '/accounts/permissions'
def get(self, request, *args, **kwargs):
cool view stuff
def post(self, request, *args, **kwargs):
cool view stuff
I'm not sure if I understood well, but one solution I can think is:
Set login_url to "/accounts/login"
override check_permissions method, and set login_url to "/accounts/permissions" inside it.
This way, if login check fail it never reaches check_permissions (I suppose), and if it fallback to check_permissions you change the redirect url, so it goes to the page you want.
thought I never tried it.

Django: How to provide context into a FormView get() method (also using request param)

I'm trying to provide some additional context into the get() method in my FormView. I need get() because I need to run some logic first, check for a potential redirect. I also need access to the request object (because I need to check session data). Can't figure out how to do it. Simplified code below..
Attempt 1:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get(self, request):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get(request)
context['message'] = self.request.session['message']
return context
No errors, but context does not come through in the template.
Attempt 2:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get_context_data(self, request, **kwargs):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context
Getting TypeError: get_context_data() takes exactly 2 arguments (1 given)
P.S. This work relates to a workaround Django's buggy messages middleware which seems to be working locally flawlessly but on live (Heroku) is not 100% reliable, renders on some pages only. Ugh, frustration setting in...
Ditch the request argument to the get_context_data method. You should also use the dispatch method to check if the user is logged in.
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def dispatch(self, *args, **kwargs):
"""Use this to check for 'user'."""
if request.session.get('user'):
return redirect('/dashboard/')
return super(LoginView, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
"""Use this to add extra context."""
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context

Categories

Resources