In order to provide progress feedback of file uploads I need to install a custom upload handler for a specific view. This is documented for "classical" Django views at
https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#modifying-upload-handlers-on-the-fly
For generic views, however, I could not find any instructions and I came up with the following:
from django.utils import importlib
from django.core.exceptions import ImproperlyConfigured
from django.views.decorators.csrf import csrf_protect
class UploadHandlerMixin(object):
'''
A mixin for Django generic views that installs a custom upload handler in front of
the current chain of upload handlers.
You specify the handler to install by overriding the 'upload_handler' attribute of
the class, specifying the module and class name in the form 'path.to.module.class':
class MyView(UploadHandlerMixin, View):
upload_handler = 'path.to.module.MyUploadHandler'
If you do not override 'upload_handler', no additional upload handler will be
installed.
If the CsrfViewMiddleware is installed (which is the default) then you must use
your view as follows in your urls.py:
from django.views.decorators.csrf import csrf_exempt
url(r'^.../$', csrf_exempt(MyView.as_view()), ...),
Internally, the UploadHandlerMixin mixin will install the upload handler and then
perform the CSRF check. (This is necessary because the CSRF check inspects
request.POST, and afterwards upload handlers cannot be changed, see documentation
link given below.)
The handler is installed as described in the Django documentation "Modifying upload handlers
on the fly", see https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#modifying-upload-handlers-on-the-fly
'''
upload_handler = None
def dispatch(self, request, *args, **kwargs):
if not self.upload_handler is None:
request.upload_handlers.insert(0, UploadHandlerMixin._instantiate_upload_handler(self.upload_handler, request))
return _uploadhandler_dispatch(request, self, *args, **kwargs)
#staticmethod
def _instantiate_upload_handler(path, *args, **kwargs):
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
mod = importlib.import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e))
except ValueError, e:
raise ImproperlyConfigured('Error importing upload handler module. Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?')
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr))
return cls(*args, **kwargs)
#csrf_protect
def _uploadhandler_dispatch(request, view, *args, **kwargs):
return super(UploadHandlerMixin, view).dispatch(request, *args, **kwargs)
Is this the "recommended way" to accomplish the task? Is it okay security-wise?
Answering myself, an alternative to a service-side solution (like Django with a custom upload handler) is a client-side solution like JQuery File Upload. It uses the fact that more recent browsers allow the upload progress to be read on the client. In my case, this was much simpler to implement and causes no additional server load.
Related
I have a DRF view where I need to ensure that uploaded files land on the filesystem and not just in memory. DRF respects Django's FILE_UPLOAD_HANDLERS setting, but I don't want to change it for my whole app, just this one view.
I know that in a regular Django view I could set request.upload_handlers to my desired value, but that doesn't seem to work in DRF. I've tried doing it from .initialize_request() in my viewset, like so:
def initialize_request(self, request, *args, **kwargs):
request.upload_handlers = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"]
return super().initialize_request(request, *args, **kwargs)
but I'm getting:
AttributeError: You cannot set the upload handlers after the upload has been processed.
What is the correct way for me to set the upload handlers for a single DRF view (in particular, the create action of a generic viewset)?
It seems like you are not assigning the upload handler in the right way
from django.core.files.uploadhandler import TemporaryFileUploadHandler
from rest_framework import viewsets
class MyUploadViewSet(viewsets.ModelViewSet):
# your view class attributes goes here....
def initialize_request(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request)] # initialization goes here
return super().initialize_request(request, *args, **kwargs)
Note
This will work as-is in all DRF class based views
I started noticing that the patch method in django rest framework doesn't actually trigger signals, post methods seem to work fine. This is what I have:
#receiver(signals.pre_save, sender=Example)
def populate_time_fields_based_on_state(sender, **kwargs):
example = kwargs.get('instance')
if example.start_datetime is None and example.projected_end_datetime is None and example.state.label == 'Assigned':
example.start_datetime = datetime.datetime.now()
example.projected_end_datetime = example.created_datetime + datetime.timedelta(
days=example.som_field)
example.save()
And I'm testing this via:
client = APIClient()
client.patch(f'/api/example/1/', {'state': 'Assigned'})
Is there a way to tell it to trigger the signal? Do I need to override the update method in my serializer? I'm trying this:
def partial_update(self, request, *args, **kwargs):
response = super().partial_update(request, *args, **kwargs)
instance = self.get_object()
instance.save()
return response
But it's quite hacky
In your app directory there should be an apps.py, see the docs for the format of it.
Generally it ends up looking like the following, as opposed to the example where they wire up the signal manually. Note that I have a "project/apps/" structure here, but just change the module name depending on where the files actually live:
#project/apps/my_app/__init__.py
default_app_config = 'project.apps.my_app.apps.MyAppConfig'
#project/apps/my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "project.apps.my_app"
verbose_name = "MyApp"
def ready(self):
from project.apps.my_app import signals
# ... other init &/or logging
Note: Feel free to delete the line in init.py, and play with the name in the app config. I'm not sure how critical they actually are
I require to check authentication via token during execution of certain views, while some views can be accessed without authentication.
So, how do i make a middleware and exclude some views from it.
Any other idea to solve this is appreciated.
I would suggest taking inspiration from the csrf middleware that Django provides
from django.utils.deprecation import MiddlewareMixin
class MyAuthenticationMiddleware(MiddlewareMixin):
def process_view(self, request, callback, callback_args, callback_kwargs):
if getattr(callback, 'my_exempt_flag', False):
return None
# Authentication goes here
# Return None if authentication was successful
# Return a HttpResponse with some error status if not successful
And create a decorator to wrap your views
from functools import wraps
def exempt_from_my_authentication_middleware(view_func):
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.my_exempt_flag = True
return wraps(view_func)(wrapped_view)
Can be used like so
#exempt_from_my_authentication_middleware
def my_view(request):
# TODO
In a Django+Wagtail project, I have a custom method which I call from the model save() method. It works - but everytime I save an instance via the wagtail admin interface, this method is called twice - why?
# models.py
from wagtail.core.models import Page
class ArticlePage(Page):
def my_method(self):
print('I will be printed twice on save...')
def save(self, *args, **kwargs):
self.my_method()
super().save(*args, **kwargs)
By using traceback.print_stack as suggested by HÃ¥ken Lid I've found out that the method is first called via
File ".../.venv/lib/python3.6/site-packages/wagtail/admin/views/pages.py", line 336, in edit
submitted_for_moderation=is_submitting,
File ".../.venv/lib/python3.6/site-packages/wagtail/core/models.py", line 653, in save_revision
self.save(update_fields=update_fields)
and the second time via
File ".../.venv/lib/python3.6/site-packages/wagtail/admin/views/pages.py", line 343, in edit
revision.publish()
File ".../.venv/lib/python3.6/site-packages/wagtail/core/models.py", line 1498, in publish
page.save()
But even with this information I'm not aware how to only trigger my method on the second save...
Env:
Django 2.0.4
Wagtail 2.0.1
In case you want to do something on page publish, You can use the page_published signals as below:
from django.dispatch import receiver
from wagtail.core.signals import page_published
#receiver(page_published)
def do_stuff_on_page_published(instance, **kwargs):
print('=======================',instance,kwargs)
In case of save, you can also check the update_fields argument in the kwargs:
def save(self, *args, **kwargs):
if kwargs.get('update_fields'):
pass # save not called from publish
# do_stuff_on_save()
else:
pass
# do_stuff_on_publish()
return super().save(*args, **kwargs)
For more info about wagtail signal code visit this link
and this for the official docs
http://docs.wagtail.io/en/v1.7/reference/signals.html
# views.py
from django.shortcuts import render_to_response
from myapp.api.resources import UserResource
def user_detail(request, username):
ur = UserResource()
user = ur.obj_get(username=username)
# Other things get prepped to go into the context then...
ur_bundle = ur.build_bundle(obj=user, request=request)
return render_to_response('myapp/user_detail.html', {
# Other things here.
"user_json": ur.serialize(None, ur.full_dehydrate(ur_bundle), 'application/json'),
})
But it gives error because obj_get() needs 2 parameters. Anyone has seen that? Am I wrong?
In the tastypie version 0.9.13 obj_get method has required parameter bundle.
def obj_get(self, bundle, **kwargs):
"""
Fetches an individual object on the resource.
This needs to be implemented at the user level. If the object can not
be found, this should raise a ``NotFound`` exception.
``ModelResource`` includes a full working version specific to Django's
``Models``.
"""
raise NotImplementedError()