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

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.

Related

How to restrict access to certain groups in django class based view

My views.py have a mix of def and ClassViews:
#login_required(login_url='login')
#allowed_users(allowed_roles=['Admin', 'Staff', 'Lite Scan'])
def litescan(request):
filteredOutput = Stock.objects.all()
val = {}...
#method_decorator(login_required(login_url='login'), name='dispatch')
class HomeView(ListView):
model = Post
template_name = 'community.html'
ordering = ['-id']
And here's my decorators.py if that is helpful:
from django.shortcuts import redirect
from django.http import HttpResponseRedirect
def unauthenticated_user(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('home')
else:
return view_func(request, *args, **kwargs)
return wrapper_func
def allowed_users(allowed_roles=[]):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
group = None
if request.user.groups.exists():
group = request.user.groups.all()[0].name
if group in allowed_roles:
return view_func(request, *args, **kwargs)
else:
url = ('/forbidden')
return HttpResponseRedirect(url)
return wrapper_func
return decorator
I found out that #login_required and #allowed_users give out an error when used with ClassView. So i used #method_decorator which brings me to the login page before redirecting to the page. However, I can not find a way to restrict access to only certain groups like Admin, Staff, Lite Scan with my ClassView.
Little help will be appreciated. Thanks!
You can use AccessMixin for your class views.
Example I found:
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class FinanceOverview(AccessMixin, TemplateMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
# This will redirect to the login view
return self.handle_no_permission()
if not self.request.user.groups.filter(name="FinanceGrp").exists():
# Redirect the user to somewhere else - add your URL here
return HttpResponseRedirect(...)
# Checks pass, let http method handlers process the request
return super().dispatch(request, *args, **kwargs)
More info found here: Use LoginRequiredMixin and UserPassesTestMixin at the same time
Relying on Django Permissions may be a far simpler approach to giving access to such a view. Rather than checking for a specific list of groups, you can assign permissions to those groups and give access to the view based on whether the user's groups have the appropriate permissions.
views.py
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionsRequiredMixin
#permission_required('foo.view_bar')
def my_view(request):
...
class MyView(PermissionRequiredMixin, DetailView):
permission_required = ('foo.view_bar', )
...

How to return an empty form in ModelFormMixin

DetailStory subclasses DetailView and ModelFormMixin thus presenting the DetailView of a certain story and a form at the end. However, on filling the form and submitting the data, the data is saved in the databases but it is still shown on the form (in addition to the one now displayed on the DetailView). How do I present an empty form after submitting it? (Here is the code sample)
class DetailStory(DetailView, ModelFormMixin):
model = Story
template_name = 'stories/detail_story.html'
context_object_name = 'detail'
form_class = CommentForm
def get(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
return DetailView.get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
if self.form.is_valid():
obj = self.form.save(commit=False)
obj.user = self.request.user
obj.memoir = self.get_object()
self.object = obj.save()
return self.get(request, *args, **kwargs)
def get_object(self):
item_id = crypt.decode(self.kwargs['story_id'])[0]
obj = get_object_or_404(Story, Q(privacy='public') | Q(user_id=self.request.user.id), pk=item_id)
return obj
get_form uses the request data to construct the form as per the docs
If the request is a POST or PUT, the request data (request.POST and request.FILES) will also be provided.
So simply don't make your post function go back through the get, just have it redirect to your required place or do anything differently to pointing it at the get function.
return redirect('mynamespace:story_detail', story_id=self.object.pk)
You may wish to read this answer for a list of technical details you should consider whilst making your application. In particular,
Redirect after a POST if that POST was successful, to prevent a refresh from submitting again.

How to catch a request in a DRF viewset?

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.

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

Django Class Based View: retrieve lastly viewed entry

I am fairly new to CBV and I have the following question:
I see that there is CreateView that would show you the "empty" form to create a new database entry and there is UpdateView that would show you the form for the existing entry for update.
What I need is some kind of mix of it: present the user with the form for the lastly viewes/updated entry, but if the database has no entries yet (e.g. new user), present the user with a default ("empty") form.
So, there are 2 points here:
Have a model that contains lastly viewed/updated entry per user: what should this model be?
Have a view that allows to present forms as specified above. Is there a generic or semi-generic way to do that in Django? What kind of CBV should I be using?
Thanks.
I haven't tested this so I'm not sure if this will work.
from django.views.generic.edit import ModelFormMixin, ProcessFormMixin
class MyView(ModelFormMixin, ProcessFormMixin):
def get(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).post(request, *args, **kwargs)

Categories

Resources