Package: django-distill
django-distilldjango-distillnow has a website. Read more at::link:https://django-distill.com/django-distillis a minimal configuration static site generator and publisher
for Django. Most Django versions are supported, however up to date versions are
advised including the Django 3.x releases.django-distillas of the 1.7 release
only supports Python 3. Python 2 support has been dropped. If you require Python 2
support please pindjango-distillto version 1.6 in your requirements.txt or
Pipfile. Python 3.6 or above is advised.django-distillextends existing Django sites with the ability to export
fully functional static sites. It is suitable for sites such as blogs that have
a mostly static front end but you still want to use a CMS to manage the
content.django-distilliterates over URLs in your Django project using easy to write
iterable functions to yield the parameters for whatever pages you want to save
as static HTML. These static files can be automatically uploaded to a bucket-style
remote container such as Amazon S3, Googe Cloud Files, Microsoft Azure Storage,
or, written to a local directory as a fully working local static version of
your project. The site generation, or distillation process, can be easily
integrated into CI/CD workflows to auto-deploy static sites on commit.django-distillcan be defined as an extension to Django to make Django
projects compatible with "Jamstack"-style site architecture.django-distillplugs directly into the existing Django framework without the
need to write custom renderers or other more verbose code. You can also integratedjango-distillwith existing dynamic sites and just generate static pages for
a small subsection of pages rather than the entire site.For static files on CDNs you can use the following 'cache buster' library to
allow for fast static media updates when pushing changes::link: meeb/django-cachekillerThere is a complete example site that creates a static blog and usesdjango-distillwithdjango-cachekillervia continuous deployment on Netlify
available here::link: meeb/django-distill-exampleInstallationInstall from pip:$pipinstalldjango-distillAdddjango_distillto yourINSTALLED_APPSin yoursettings.py:INSTALLED_APPS=[# ... other apps here ...'django_distill',]That's it.Limitationsdjango-distillgenerates static pages and therefore only views which allowGETrequests that return anHTTP 200status code are supported.It is assumed you are using URI parameters such as/blog/123-abcand not
querystring parameters such as/blog?post_id=123&title=abc. Querystring
parameters do not make sense for static page generation for obvious reasons.Additionally With one-off static pages dynamic internationalisation won't work
so all files are generated using theLANGUAGE_CODEvalue in yoursettings.py.Static media files such as images and style sheets are copied from your static
media directory defined inSTATIC_ROOT. This means that you will want to run./manage.py collectstaticbeforeyou run./manage.py distill-localif you have made changes to static media.django-distilldoesn't chain this
request by design, however you can enable it with the--collectstaticargument.UsageAssuming you have an existing Django project, edit aurls.pyto include thedistill_pathfunction which replaces Django's standardpathfunction and
supports the new keyword argumentsdistill_funcanddistill_file.Thedistill_funcargument should be provided with a function or callable
class that returns an iterable orNone.Thedistill_fileargument is entirely optional and allows you to override the
URL that would otherwise be generated from the reverse of the URL regex. This
allows you to rename URLs like/exampleto any other name likeexample.html. As of v0.8 any URIs ending in a slash/are automatically
modified to end in/index.html. You can use format string parameters in thedistill_fileto customise the file name, arg values from the URL will be
substituted in, for example{}for positional args or{param_name}for
named args.An example distill setup for a theoretical blogging app would be:# Replaces the standard django.conf.path, identical syntaxfromdjango_distillimportdistill_path# Views and models from a theoretical blogging appfromblog.viewsimportPostIndex,PostView,PostYearfromblog.modelsimportPostdefget_index():# The index URI path, '', contains no parameters, named or otherwise.# You can simply just return nothing here.returnNonedefget_all_blogposts():# This function needs to return an iterable of dictionaries. Dictionaries# are required as the URL this distill function is for has named parameters.# You can just export a small subset of values here if you wish to# limit what pages will be generated.forpostinPost.objects.all():yield{'blog_id':post_id,'blog_title':post.title}defget_years():# You can also just return an iterable containing static strings if the# URL only has one argument and you are using positional URL parameters:return(2014,2015)# This is really just shorthand for ((2014,), (2015,))urlpatterns=(# e.g. / the blog indexdistill_path('',PostIndex.as_view(),name='blog-index',# Note that for paths which have no paramters# distill_func is optionaldistill_func=get_index,# '' is not a valid file name! override it to index.htmldistill_file='index.html'),# e.g. /post/123-some-post-title using named parametersdistill_path('post/<int:blog_id>-<slug:blog_title>.html',PostView.as_view(),name='blog-post',distill_func=get_all_blogposts),# e.g. /posts-by-year/2015 using positional parameters# url ends in / so file path will have /index.html appendeddistill_path('posts-by-year/<int:year>/',PostYear.as_view(),name='blog-year',distill_func=get_years),)Your site will still function identically with the above changes. Internally
thedistill_funcanddistill_fileparameters are removed and the URL is
passed back to Django for normal processing. This has no runtime performance
impact as this happens only once upon starting the application.If your path has no URI paramters, such as/or/some-static-urlyou do
not have to specify thedistill_funcparameter if you don't want to. As for
paths with no parameters thedistill_funcalways returnsNone, this is set
as the default behaviour fordistill_funcs.You can use thedistill_re_pathfunction as well, which replaces the defaultdjango.urls.re_pathfunction. Its usage is identical to the above:fromdjango_distillimportdistill_re_pathurlpatterns=(distill_re_path(r'some/regex'SomeOtherView.as_view(),name='url-other-view',distill_func=some_other_func),)If you are using an older version of Django in the 1.x series you can use thedistill_urlfunction instead which replaces thedjango.conf.urls.urlordjango.urls.urlfunctions. Its usage is identical to the above:fromdjango_distillimportdistill_urlurlpatterns=(distill_url(r'some/regex'SomeView.as_view(),name='url-view',distill_func=some_func),)Parameters in file namesYou can standard Python string formatting indistill_fileas well to enable
you to change the output file path for a file if you wish. Note this does not
update the URL used by Django so if you use this make sure yourpathpattern
matches thedistill_filepattern or your links might not work in Django. An
example:# Override file path with parameters. Values are taken from the URL patternurlpatterns=(distill_path('post/<int:blog_id>-<slug:blog_title>.html',PostView.as_view(),name='blog-post',distill_func=get_all_blogposts,distill_file="post/{blog_id}-{blog_title}.html")Non-standard status codesAll views rendered bydjango-distillinto static pages must return an HTTP 200 status
code. If for any reason you need to render a view which does not return an HTTP 200
status code, for example you also want to statically generate a 404 page which has a
view which (correctly) returns an HTTP 404 status code you can use thedistill_status_codesoptional argument to a view. For example:fromdjango_distillimportdistill_urlurlpatterns=(distill_url(r'some/regex'SomeView.as_view(),name='url-view',distill_status_codes=(200,404),distill_func=some_func),)The optionaldistill_status_codesargument accepts a tuple of status codes as integers
which are permitted for the view to return without raising an error. By default this is
set to(200,)but you can override it if you need to for your site.Tracking Django's URL function supportdjango-distillwill mirror whatever your installed version of Django supports,
therefore at some point thedistill_urlfunction will cease working in the future
when Django 2.x itself depreciates thedjango.conf.urls.urlanddjango.urls.urlfunctions. You can usedistill_re_pathas a drop-in replacement. It is advisable to
usedistill_pathordistill_re_pathif you're building a new site now.Thedistill-localcommandOnce you have wrapped the URLs you want to generate statically you can now
generate a complete functioning static site with:$./manage.pydistill-local[optional/path/to/export/directory]Under the hood this simply iterates all URLs registered withdistill_urland
generates the pages for them using parts of the Django testing framework to
spoof requests. Once the site pages have been rendered then files from theSTATIC_ROOTare copied over. Existing files with the same name are replaced in
the target directory and orphan files are deleted.distill-localsupports the following optional arguments:--collectstatic: Automatically runcollectstaticon your site before
rendering, this is just a shortcut to save you typing an extra command.--quiet: Disable all output other than asking confirmation questions.--force: Assume 'yes' to all confirmation questions.--exclude-staticfiles: Do not copy any static files at all, only render output from
Django views.NoteIf any of your views contain a Python error then rendering will fail
then the stack trace will be printed to the terminal and the rendering command
will exit with a status code of 1.Thedistill-publishcommand$./manage.pydistill-publish[optionaldestinationhere]If you have configured at least one publishing destination (see below) you can
use thedistill-publishcommand to publish the site to a remote location.This will perform a full synchronisation, removing any remote files that are no
longer present in the generated static site and uploading any new or changed
files. The site will be built into a temporary directory locally first when
publishing which is deleted once the site has been published. Each file will be
checked that it has been published correctly by requesting it via thePUBLIC_URL.distill-publishsupports the following optional arguments:--collectstatic: Automatically runcollectstaticon your site before
rendering, this is just a shortcut to save you typing an extra command.--quiet: Disable all output other than asking confirmation questions.--force: Assume 'yes' to all confirmation questions.--exclude-staticfiles: Do not copy any static files at all, only render output from
Django views.--skip-verify: Do not test if files are correctly uploaded on the server.--ignore-remote-content: Do not fetch the list of remote files. It means that all
files will be uploaded, and no existing remote file will be  deleted. This can be
useful if you have a lot of files on the remote server, and you know that you want
to update most of them, and you don't care if old files remain on the server.--parallel-publish [number of threads]: Publish files in parallel on multiple
threads, this can speed up publishing. Defaults to1thread.Notethat this means if you use--forceand--quietthat the output
directory will have all files not part of the site export deleted without any
confirmation.NoteIf any of your views contain a Python error then rendering will fail
then the stack trace will be printed to the terminal and the rendering command
will exit with a status code of 1.Thedistill-test-publishcommand$./manage.pydistill-test-publish[optionaldestinationhere]This will connect to your publishing target, authenticate to it, upload a
randomly named file, verify it exists on thePUBLIC_URLand then delete it
again. Use this to check your publishing settings are correct.distill-test-publishhas no arguments.Optional configuration settingsYou can set the following optionalsettings.pyvariables:DISTILL_DIR: string, default directory to export to:DISTILL_DIR='/path/to/export/directory'DISTILL_PUBLISH: dictionary, like Django'ssettings.DATABASES, supportsdefault:DISTILL_PUBLISH={'default':{...options...},'some-other-target':{...options...},}DISTILL_SKIP_ADMIN_DIRS: bool, defaults toTrueDISTILL_SKIP_ADMIN_DIRS=TrueSetDISTILL_SKIP_ADMIN_DIRStoFalseif you wantdjango-distillto also copy over
static files in thestatic/admindirectory. Usually, these are not required or
desired for statically generated sites. The default behaviour is to skip static admin
files.Writing single filesAs ofdjango-distillversion3.0.0you can use thedjango_distill.renderer.render_single_filemethod to write out a single file
to disk usingdjango_distill. This is useful for writing out single files to disk,
for example, you have a Django site which has some static files in a directory
written bydjango_distillbut the rest of the site is a normal dynamic Django site.
You can update a static HTML file every time a model instance is saved. You can
use single file writing with signals to achieve this. For example:# in models.pyfromdjango.db.models.signalsimportpost_savefromdjango.dispatchimportreceiverfromdjango_distill.rendererimportrender_single_file@receiver(post_save,sender=SomeBlogPostModel)defwrite_blog_post_static_file_post_save(sender,**kwargs):render_single_file('/path/to/output/directory','blog-post-view-name',blog_id=sender.pk,blog_slug=sender.slug)The syntax forrender_single_fileis similar to Django'surl.reverse. The full
usage interface is:render_single_file('/path/to/output/directory','view-name-set-in-urls-py',*view_args,**view_kwargs)For example, if you had a blog post URL defined as:# in urls.pydistill_path('post/<int:blog_id>_<slug:blog_slug>.html',PostView.as_view(),name='blog-post',distill_func=get_all_blogposts),Your usage would be:render_single_file('/path/to/output/directory','blog-post',blog_id=123,blog_slug='blog-title-slug',)which would write out the contents of/post/123_blog-title-slug.htmlinto/path/to/output/directoryas the file/path/to/output/directory/post/123_blog-title-slug.html. Note any required
sub-directories (/path/to/output/directory/postin this example) will be
automatically created if they don't already exist. Alldjango-distillrules
apply, such as URLs ending in/will be saved as/index.htmlto make sense
for a physical file on disk.Also note thatrender_single_filecan only be imported and used into an
initialised Django project.Publishing targetsYou can automatically publish sites to various supported remote targets through
backends just like how you can use MySQL, SQLite, PostgreSQL etc. with
Django by changing the backend database engine. Currently the engines supported
bydjango-distillare:django_distill.backends.amazon_s3: Publish to an Amazon S3 bucket. Requires
the Python libraryboto3($ pip install django-distill[amazon]). The bucket
must already exist (use the AWS control panel). Options:'some-s3-container':{'ENGINE':'django_distill.backends.amazon_s3','PUBLIC_URL':'http://.../','ACCESS_KEY_ID':'...','SECRET_ACCESS_KEY':'...','BUCKET':'...',},django_distill.backends.google_storage: Publish to a Google Cloud Storage
bucket. Requires the Python librariesgoogle-api-python-clientandgoogle-cloud-storage($ pip install django-distill[google]). The bucket
must already exist and be set up to host a public static website (use the
Google Cloud control panel). Options:'some-google-storage-bucket':{'ENGINE':'django_distill.backends.google_storage','PUBLIC_URL':'https://storage.googleapis.com/[bucket.name.here]/','BUCKET':'[bucket.name.here]','JSON_CREDENTIALS':'/path/to/some/credentials.json',},Note thatJSON_CREDENTIALSis optional; if it is not specified, the google libraries
will try other authentication methods, in the search order described here:https://cloud.google.com/docs/authentication/application-default-credentials(e.g. theGOOGLE_APPLICATION_CREDENTIALSenvironment variable, attached service account, etc).django_distill.backends.microsoft_azure_storage: Publish to a Microsoft
Azure Blob Storage container. Requires the Python libraryazure-storage-blob($ pip install django-distill[microsoft]). The storage
account must already exist and be set up to host a public static website
(use the Microsoft Azure control panel). Options:'some-microsoft-storage-account':{'ENGINE':'django_distill.backends.microsoft_azure_storage','PUBLIC_URL':'https://[storage-account-name]...windows.net/','CONNECTION_STRING':'...',},Note that each Azure storage account supports one static website using the
magic container$webwhich is wheredjango-distillwill attempt to
publish your site.TestsThere is a minimal test suite, you can run it by cloing this repository,
installing the required dependancies inrequirements.txtthen execuiting:# ./run-tests.pyContributingAll properly formatted and sensible pull requests, issues and comments are
welcome.