def bulk_download(request, download_type, items=None): r""" By default, ``aristotle_mdr.views.bulk_download`` is called whenever a URL matches the pattern defined in ``aristotle_mdr.urls_aristotle``:: bulk_download/(?P<download_type>[a-zA-Z0-9\-\.]+)/? This is passed into ``bulk_download`` which takes the items GET arguments from the request and determines if a user has permission to view the requested items. For any items the user can download they are exported in the desired format as described in ``aristotle_mdr.views.download``. If the requested module is able to be imported, ``downloader.py`` from the given module is imported, this file **MUST** have a ``bulk_download`` function defined which returns a Django ``HttpResponse`` object of some form. """ # downloadOpts = fetch_aristotle_settings().get('DOWNLOADERS', []) items = request.GET.getlist('items') download_opts = fetch_aristotle_downloaders() get_params = request.GET.copy() get_params.setdefault('bulk', True) for kls in download_opts: if download_type == kls.download_type: try: # properties for download template properties = kls.get_bulk_download_config(request, items) if get_params.get('public', False): properties['user'] = False res = kls.bulk_download.delay(properties, items) if not properties.get('title', ''): properties['title'] = 'Auto-generated document' get_params.pop('title') get_params.setdefault('title', properties['title']) response = redirect('{}?{}'.format( reverse('aristotle:preparing_download', args=[download_type]), urlencode(get_params, True) )) download_key = request.session.get(download_utils.get_download_session_key(get_params, download_type)) if download_key: async_result(download_key).forget() request.session[download_utils.get_download_session_key(get_params, download_type)] = res.id return response except TemplateDoesNotExist: debug = getattr(settings, 'DEBUG') if debug: raise # Maybe another downloader can serve this up continue raise Http404
def get_async_download(request, download_type): """ This will return the download if the download is cached in redis. Checks: 1. check if the download has expired 2. check if there is no key to download. If there is not :param request: :param download_type: type of download :return: """ items = request.GET.getlist('items', None) debug = getattr(settings, 'DEBUG') download_key = download_utils.get_download_session_key(request.GET, download_type) try: res_id = request.session[download_key] except KeyError: logger.exception('There is no key for request') if debug: raise raise Http404 job = async_result(res_id) if not job.successful(): if job.status == 'PENDING': logger.exception('There is no task or you shouldn\'t be on this page yet') raise Http404 else: exc = job.get(propagate=False) logger.exception('Task {0} raised exception: {1!r}\n{2!r}'.format(res_id, exc, job.traceback)) return HttpResponseServerError('cant produce document, Try again') job.forget() try: doc, mime_type, properties = cache.get( download_utils.get_download_cache_key(items, request=request, download_type=download_type), (None, '', '') ) except ValueError: if debug: raise logger.exception('Should unpack 3 values from the cache', ValueError) return HttpResponseServerError('Cant unpack values') if not doc: if debug: raise ValueError('No document in the cache') # TODO: Need a design to avoid loop and refactor this to redirect to preparing-download return HttpResponseServerError('No document in cache') response = HttpResponse(doc, content_type=mime_type) response['Content-Disposition'] = 'attachment; filename="{}.{}"'.format(request.GET.get('title'), download_type) for key, val in properties.items(): response[key] = val del request.session[download_key] return response
def get(self, request, *args, **kwargs): if 'taskid' not in self.kwargs: raise Http404 task_id = self.kwargs['taskid'] # Check if the job exists job = async_result(task_id) context = { 'is_ready': False, 'is_expired': False, 'state': job.state, 'file_details': {} } if job.ready(): if job.state == 'SUCCESS': context['result'] = job.result context['is_ready'] = True context['is_expired'] = False return JsonResponse(context)
def download(request, download_type, iid=None): r""" By default, ``aristotle_mdr.views.download`` is called whenever a URL matches the pattern defined in ``aristotle_mdr.urls_aristotle``:: download/(?P<download_type>[a-zA-Z0-9\-\.]+)/(?P<iid>\d+)/? This is passed into ``download`` which resolves the item id (``iid``), and determines if a user has permission to view the requested item with that id. If a user is allowed to download this file, ``download`` iterates through each download type defined in ``ARISTOTLE_SETTINGS.DOWNLOADERS``. A download option tuple takes the following form form:: ('file_type','display_name','font_awesome_icon_name','module_name'), With ``file_type`` allowing only ASCII alphanumeric and underscores, ``display_name`` can be any valid python string, ``font_awesome_icon_name`` can be any Font Awesome icon and ``module_name`` is the name of the python module that provides a downloader for this file type. For example, the Aristotle-PDF with Aristotle-MDR is a PDF downloader which has the download definition tuple:: ('pdf','PDF','fa-file-pdf-o','aristotle_pdr'), Where a ``file_type`` multiple is defined multiple times, **the last matching instance in the tuple is used**. Next, the module that is defined for a ``file_type`` is dynamically imported using ``exec``, and is wrapped in a ``try: except`` block to catch any exceptions. If the ``module_name`` does not match the regex ``^[a-zA-Z0-9\_]+$`` ``download`` raises an exception. If the module is able to be imported, ``downloader.py`` from the given module is imported, this file **MUST** have a ``download`` function defined which returns a Django ``HttpResponse`` object of some form. """ item = MDR._concept.objects.get_subclass(pk=iid) item = get_if_user_can_view(item.__class__, request.user, iid) if not item: if request.user.is_anonymous(): return redirect(reverse('friendly_login') + '?next=%s' % request.path) else: raise PermissionDenied get_params = request.GET.copy() get_params.setdefault('items', iid) download_opts = fetch_aristotle_downloaders() for kls in download_opts: if download_type == kls.download_type: try: # properties requested for the file requested kls.item = item properties = kls.get_download_config(request, iid) if get_params.get('public', False): properties['user'] = False res = kls.download.delay(properties, iid) get_params.setdefault('title', properties.get('title', 'Auto-Generated Document')) response = redirect('{}?{}'.format( reverse('aristotle:preparing_download', args=[download_type]), urlencode(get_params, True) )) download_key = request.session.get(download_utils.get_download_session_key(get_params, download_type)) if download_key: async_result(download_key).forget() request.session[download_utils.get_download_session_key(get_params, download_type)] = res.id return response except TemplateDoesNotExist: debug = getattr(settings, 'DEBUG') if debug: raise # Maybe another downloader can serve this up continue raise Http404
def prepare_async_download(request, download_type): """ This view lets user know that the download is being prepared. Checks: 1. check if there is a celery task id present in the session. 2. check if the celery task id expired/already downloaded. 3. check if the job is ready. :param request: request object from the API call. :param download_type: type of download :return: appropriate HTTP response object """ try: get_params = request.GET.copy() items = get_params.getlist('items') except KeyError: return HttpResponseBadRequest() download_key = download_utils.get_download_session_key(get_params, download_type) if get_params.get('format', False): get_params.pop('format') start_new_download = False try: # Check if the job exists res_id = request.session[download_key] job = async_result(res_id) if job.status == states.PENDING: # make sure you don't call a forget in the rest of the function or else it will go into infinite loop del request.session[download_key] job.forget() # Raising key error because key is invalid raise KeyError except KeyError: start_new_download = True if start_new_download: if get_params.get('bulk'): redirect_url = reverse('aristotle:bulk_download', args=[download_type]) else: redirect_url = reverse('aristotle:download', args=[download_type, items[0]]) return redirect('{}?{}'.format( redirect_url, urlencode(get_params, True) )) template = 'aristotle_mdr/downloads/creating_download.html' context = {} download_url = '{}?{}'.format( reverse('aristotle:start_download', args=[download_type]), urlencode(get_params, True) ) context['items'] = items context['file_details'] = { 'title': request.GET.get('title', 'Auto Generated Document'), 'items': ', '.join([MDR._concept.objects.get_subclass(pk=int(iid)).name for iid in items]), 'format': CONSTANTS.FILE_FORMAT[download_type], 'is_bulk': request.GET.get('bulk', False), 'ttl': int(CONSTANTS.TIME_TO_DOWNLOAD / 60), 'isReady': False } if job.ready(): context['file_details']['download_url'] = download_url context['is_ready'] = True context['is_expired'] = False if not cache.get(download_utils.get_download_cache_key(items, request=request, download_type=download_type)): context['is_expired'] = True del request.session[download_key] job.forget() if request.GET.get('format') == 'json': return JsonResponse(context) return render(request, template, context=context)