VirtualDownloadView serves files that do not live on disk. Use it when you want to stream a file which content is dynamically generated or which lives in memory.

It is all about overriding VirtualDownloadView.get_file() method so that it returns a suitable file wrapper...


Current implementation does not support reverse-proxy optimizations, because content is actually generated within Django, not stored in some third-party place.

Base options

VirtualDownloadView inherits from DownloadMixin, which has various options such as basename or attachment.

Serve text (string or unicode) or bytes

Let’s consider you build text dynamically, as a bytes or string or unicode object. Serve it with Django’s builtin ContentFile wrapper:

from django.core.files.base import ContentFile

from django_downloadview import VirtualDownloadView

class TextDownloadView(VirtualDownloadView):
    def get_file(self):
        """Return :class:`django.core.files.base.ContentFile` object."""
        return ContentFile(b"Hello world!\n", name='hello-world.txt')

Serve StringIO

StringIO object lives in memory. Let’s wrap it in some download view via VirtualFile:

from six import StringIO

from django_downloadview import VirtualDownloadView
from django_downloadview import VirtualFile

class StringIODownloadView(VirtualDownloadView):
    def get_file(self):
        """Return wrapper on ``six.StringIO`` object."""
        file_obj = StringIO(u"Hello world!\n")
        return VirtualFile(file_obj, name='hello-world.txt')

Stream generated content

Let’s consider you have a generator function (yield) or an iterator object (__iter__()):

def generate_hello():
    yield u'Hello '
    yield u'world!'
    yield u'\n'

Stream generated content using VirtualDownloadView, VirtualFile and BytesIteratorIO:

from django_downloadview import VirtualDownloadView
from django_downloadview import VirtualFile
from django_downloadview import TextIteratorIO

class GeneratedDownloadView(VirtualDownloadView):
    def get_file(self):
        """Return wrapper on ``StringIteratorIO`` object."""
        file_obj = TextIteratorIO(generate_hello())
        return VirtualFile(file_obj, name='hello-world.txt')

API reference

class django_downloadview.views.virtual.VirtualDownloadView(**kwargs)

Bases: django_downloadview.views.base.BaseDownloadView

Serve not-on-disk or generated-on-the-fly file.

Override the get_file() method to customize file wrapper.

was_modified_since(file_instance, since)

Delegate to file wrapper’s was_modified_since, or return True.

This is the implementation of an edge case: when files are generated on the fly, we cannot guess whether they have been modified or not. If the file wrapper implements was_modified_since() method, then we trust it. Otherwise it is safer to suppose that the file has been modified.

This behaviour prevents file size to be computed on the Django side. Because computing file size means iterating over all the file contents, and we want to avoid that whenever possible. As an example, it could reduce all the benefits of working with dynamic file generators... which is a major feature of virtual files.