%global _empty_manifest_terminate_build 0 Name: python-django-distill Version: 3.1.3 Release: 1 Summary: Static site renderer and publisher for Django. License: MIT URL: https://github.com/meeb/django-distill Source0: https://mirrors.nju.edu.cn/pypi/web/packages/d1/24/f2ae21a144b91eea0bf1083e267e819ca03c52cf1b477518f9775fd51787/django-distill-3.1.3.tar.gz BuildArch: noarch %description # django-distill `django-distill` now has a website. Read more at: ## :link: https://django-distill.com/ `django-distill` is 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-distill` as of the 1.7 release only supports Python 3. Python 2 support has been dropped. If you require Python 2 support please pin `django-distill` to version 1.6 in your requirements.txt or Pipfile. Python 3.6 or above is advised. `django-distill` extends 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-distill` iterates 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-distill` can be defined as an extension to Django to make Django projects compatible with "Jamstack"-style site architecture. `django-distill` plugs directly into the existing Django framework without the need to write custom renderers or other more verbose code. You can also integrate `django-distill` with 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-cachekiller](https://github.com/meeb/django-cachekiller) There is a complete example site that creates a static blog and uses `django-distill` with `django-cachekiller` via continuous deployment on Netlify available here: [:link: meeb/django-distill-example](https://github.com/meeb/django-distill-example) # Installation Install from pip: ```bash $ pip install django-distill ``` Add `django_distill` to your `INSTALLED_APPS` in your `settings.py`: ```python INSTALLED_APPS = [ # ... other apps here ... 'django_distill', ] ``` That's it. # Limitations `django-distill` generates static pages and therefore only views which allow `GET` requests that return an `HTTP 200` status code are supported. It is assumed you are using URI parameters such as `/blog/123-abc` and 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 the `LANGUAGE_CODE` value in your `settings.py`. Static media files such as images and style sheets are copied from your static media directory defined in `STATIC_ROOT`. This means that you will want to run `./manage.py collectstatic` **before** you run `./manage.py distill-local` if you have made changes to static media. `django-distill` doesn't chain this request by design, however you can enable it with the `--collectstatic` argument. # Usage Assuming you have an existing Django project, edit a `urls.py` to include the `distill_path` function which replaces Django's standard `path` function and supports the new keyword arguments `distill_func` and `distill_file`. The `distill_func` argument should be provided with a function or callable class that returns an iterable or `None`. The `distill_file` argument 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 `/example` to any other name like `example.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 the `distill_file` to 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: ```python # Replaces the standard django.conf.path, identical syntax from django_distill import distill_path # Views and models from a theoretical blogging app from blog.views import PostIndex, PostView, PostYear from blog.models import Post def get_index(): # The index URI path, '', contains no parameters, named or otherwise. # You can simply just return nothing here. return None def get_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. for post in Post.objects.all(): yield {'blog_id': post_id, 'blog_title': post.title} def get_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 index distill_path('', PostIndex.as_view(), name='blog-index', # Note that for paths which have no paramters # distill_func is optional distill_func=get_index, # '' is not a valid file name! override it to index.html distill_file='index.html'), # e.g. /post/123-some-post-title using named parameters distill_path('post/-.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 appended distill_path('posts-by-year//', PostYear.as_view(), name='blog-year', distill_func=get_years), ) ``` Your site will still function identically with the above changes. Internally the `distill_func` and `distill_file` parameters 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-url` you do not have to specify the `distill_func` parameter if you don't want to. As for paths with no parameters the `distill_func` always returns `None`, this is set as the default behaviour for `distill_func`s. You can use the `distill_re_path` function as well, which replaces the default `django.urls.re_path` function. Its usage is identical to the above: ```python from django_distill import distill_re_path urlpatterns = ( 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 the `distill_url` function instead which replaces the `django.conf.urls.url` or `django.urls.url` functions. Its usage is identical to the above: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_func=some_func), ) ``` ### Parameters in file names You can standard Python string formatting in `distill_file` as 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 your `path` pattern matches the `distill_file` pattern or your links might not work in Django. An example: ```python # Override file path with parameters. Values are taken from the URL pattern urlpatterns = ( distill_path('post/-.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts, distill_file="post/{blog_id}-{blog_title}.html" ) ``` ### Non-standard status codes All views rendered by `django-distill` into 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 the `distill_status_codes` optional argument to a view. For example: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_status_codes=(200, 404), distill_func=some_func), ) ``` The optional `distill_status_codes` argument 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 support `django-distill` will mirror whatever your installed version of Django supports, therefore at some point the `distill_url` function will cease working in the future when Django 2.x itself depreciates the `django.conf.urls.url` and `django.urls.url` functions. You can use `distill_re_path` as a drop-in replacement. It is advisable to use `distill_path` or `distill_re_path` if you're building a new site now. # The `distill-local` command Once you have wrapped the URLs you want to generate statically you can now generate a complete functioning static site with: ```bash $ ./manage.py distill-local [optional /path/to/export/directory] ``` Under the hood this simply iterates all URLs registered with `distill_url` and 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 the `STATIC_ROOT` are copied over. Existing files with the same name are replaced in the target directory and orphan files are deleted. `distill-local` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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. **Note** If 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. # The `distill-publish` command ```bash $ ./manage.py distill-publish [optional destination here] ``` If you have configured at least one publishing destination (see below) you can use the `distill-publish` command 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 the `PUBLIC_URL`. `distill-publish` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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 to `1` thread. **Note** that this means if you use `--force` and `--quiet` that the output directory will have all files not part of the site export deleted without any confirmation. **Note** If 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. # The `distill-test-publish` command ```bash $ ./manage.py distill-test-publish [optional destination here] ``` This will connect to your publishing target, authenticate to it, upload a randomly named file, verify it exists on the `PUBLIC_URL` and then delete it again. Use this to check your publishing settings are correct. `distill-test-publish` has no arguments. # Optional configuration settings You can set the following optional `settings.py` variables: **DISTILL_DIR**: string, default directory to export to: ```python DISTILL_DIR = '/path/to/export/directory' ``` **DISTILL_PUBLISH**: dictionary, like Django's `settings.DATABASES`, supports `default`: ```python DISTILL_PUBLISH = { 'default': { ... options ... }, 'some-other-target': { ... options ... }, } ``` **DISTILL_SKIP_ADMIN_DIRS**: bool, defaults to `True` ```python DISTILL_SKIP_ADMIN_DIRS = True ``` Set `DISTILL_SKIP_ADMIN_DIRS` to `False` if you want `django-distill` to also copy over static files in the `static/admin` directory. Usually, these are not required or desired for statically generated sites. The default behaviour is to skip static admin files. # Writing single files As of `django-distill` version `3.0.0` you can use the `django_distill.renderer.render_single_file` method to write out a single file to disk using `django_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 by `django_distill` but 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: ```python # in models.py from django.db.models.signals import post_save from django.dispatch import receiver from django_distill.renderer import render_single_file @receiver(post_save, sender=SomeBlogPostModel) def write_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 for `render_single_file` is similar to Django's `url.reverse`. The full usage interface is: ```python 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: ```python # in urls.py distill_path('post/_.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts), ``` Your usage would be: ```python 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.html` into `/path/to/output/directory` as the file `/path/to/output/directory/post/123_blog-title-slug.html`. Note any required sub-directories (`/path/to/output/directory/post` in this example) will be automatically created if they don't already exist. All `django-distill` rules apply, such as URLs ending in `/` will be saved as `/index.html` to make sense for a physical file on disk. Also note that `render_single_file` can only be imported and used into an initialised Django project. # Publishing targets You 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 by `django-distill` are: **django_distill.backends.amazon_s3**: Publish to an Amazon S3 bucket. Requires the Python library `boto3` (`$ pip install django-distill[amazon]`). The bucket must already exist (use the AWS control panel). Options: ```python '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 libraries `google-api-python-client` and `google-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: ```python '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 that `JSON_CREDENTIALS` is 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. the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, attached service account, etc). **django_distill.backends.microsoft_azure_storage**: Publish to a Microsoft Azure Blob Storage container. Requires the Python library `azure-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: ```python '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 `$web` which is where `django-distill` will attempt to publish your site. # Tests There is a minimal test suite, you can run it by cloing this repository, installing the required dependancies in `requirements.txt` then execuiting: ```bash # ./run-tests.py ``` # Contributing All properly formatted and sensible pull requests, issues and comments are welcome. %package -n python3-django-distill Summary: Static site renderer and publisher for Django. Provides: python-django-distill BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: python3-pip %description -n python3-django-distill # django-distill `django-distill` now has a website. Read more at: ## :link: https://django-distill.com/ `django-distill` is 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-distill` as of the 1.7 release only supports Python 3. Python 2 support has been dropped. If you require Python 2 support please pin `django-distill` to version 1.6 in your requirements.txt or Pipfile. Python 3.6 or above is advised. `django-distill` extends 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-distill` iterates 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-distill` can be defined as an extension to Django to make Django projects compatible with "Jamstack"-style site architecture. `django-distill` plugs directly into the existing Django framework without the need to write custom renderers or other more verbose code. You can also integrate `django-distill` with 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-cachekiller](https://github.com/meeb/django-cachekiller) There is a complete example site that creates a static blog and uses `django-distill` with `django-cachekiller` via continuous deployment on Netlify available here: [:link: meeb/django-distill-example](https://github.com/meeb/django-distill-example) # Installation Install from pip: ```bash $ pip install django-distill ``` Add `django_distill` to your `INSTALLED_APPS` in your `settings.py`: ```python INSTALLED_APPS = [ # ... other apps here ... 'django_distill', ] ``` That's it. # Limitations `django-distill` generates static pages and therefore only views which allow `GET` requests that return an `HTTP 200` status code are supported. It is assumed you are using URI parameters such as `/blog/123-abc` and 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 the `LANGUAGE_CODE` value in your `settings.py`. Static media files such as images and style sheets are copied from your static media directory defined in `STATIC_ROOT`. This means that you will want to run `./manage.py collectstatic` **before** you run `./manage.py distill-local` if you have made changes to static media. `django-distill` doesn't chain this request by design, however you can enable it with the `--collectstatic` argument. # Usage Assuming you have an existing Django project, edit a `urls.py` to include the `distill_path` function which replaces Django's standard `path` function and supports the new keyword arguments `distill_func` and `distill_file`. The `distill_func` argument should be provided with a function or callable class that returns an iterable or `None`. The `distill_file` argument 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 `/example` to any other name like `example.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 the `distill_file` to 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: ```python # Replaces the standard django.conf.path, identical syntax from django_distill import distill_path # Views and models from a theoretical blogging app from blog.views import PostIndex, PostView, PostYear from blog.models import Post def get_index(): # The index URI path, '', contains no parameters, named or otherwise. # You can simply just return nothing here. return None def get_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. for post in Post.objects.all(): yield {'blog_id': post_id, 'blog_title': post.title} def get_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 index distill_path('', PostIndex.as_view(), name='blog-index', # Note that for paths which have no paramters # distill_func is optional distill_func=get_index, # '' is not a valid file name! override it to index.html distill_file='index.html'), # e.g. /post/123-some-post-title using named parameters distill_path('post/-.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 appended distill_path('posts-by-year//', PostYear.as_view(), name='blog-year', distill_func=get_years), ) ``` Your site will still function identically with the above changes. Internally the `distill_func` and `distill_file` parameters 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-url` you do not have to specify the `distill_func` parameter if you don't want to. As for paths with no parameters the `distill_func` always returns `None`, this is set as the default behaviour for `distill_func`s. You can use the `distill_re_path` function as well, which replaces the default `django.urls.re_path` function. Its usage is identical to the above: ```python from django_distill import distill_re_path urlpatterns = ( 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 the `distill_url` function instead which replaces the `django.conf.urls.url` or `django.urls.url` functions. Its usage is identical to the above: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_func=some_func), ) ``` ### Parameters in file names You can standard Python string formatting in `distill_file` as 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 your `path` pattern matches the `distill_file` pattern or your links might not work in Django. An example: ```python # Override file path with parameters. Values are taken from the URL pattern urlpatterns = ( distill_path('post/-.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts, distill_file="post/{blog_id}-{blog_title}.html" ) ``` ### Non-standard status codes All views rendered by `django-distill` into 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 the `distill_status_codes` optional argument to a view. For example: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_status_codes=(200, 404), distill_func=some_func), ) ``` The optional `distill_status_codes` argument 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 support `django-distill` will mirror whatever your installed version of Django supports, therefore at some point the `distill_url` function will cease working in the future when Django 2.x itself depreciates the `django.conf.urls.url` and `django.urls.url` functions. You can use `distill_re_path` as a drop-in replacement. It is advisable to use `distill_path` or `distill_re_path` if you're building a new site now. # The `distill-local` command Once you have wrapped the URLs you want to generate statically you can now generate a complete functioning static site with: ```bash $ ./manage.py distill-local [optional /path/to/export/directory] ``` Under the hood this simply iterates all URLs registered with `distill_url` and 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 the `STATIC_ROOT` are copied over. Existing files with the same name are replaced in the target directory and orphan files are deleted. `distill-local` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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. **Note** If 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. # The `distill-publish` command ```bash $ ./manage.py distill-publish [optional destination here] ``` If you have configured at least one publishing destination (see below) you can use the `distill-publish` command 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 the `PUBLIC_URL`. `distill-publish` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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 to `1` thread. **Note** that this means if you use `--force` and `--quiet` that the output directory will have all files not part of the site export deleted without any confirmation. **Note** If 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. # The `distill-test-publish` command ```bash $ ./manage.py distill-test-publish [optional destination here] ``` This will connect to your publishing target, authenticate to it, upload a randomly named file, verify it exists on the `PUBLIC_URL` and then delete it again. Use this to check your publishing settings are correct. `distill-test-publish` has no arguments. # Optional configuration settings You can set the following optional `settings.py` variables: **DISTILL_DIR**: string, default directory to export to: ```python DISTILL_DIR = '/path/to/export/directory' ``` **DISTILL_PUBLISH**: dictionary, like Django's `settings.DATABASES`, supports `default`: ```python DISTILL_PUBLISH = { 'default': { ... options ... }, 'some-other-target': { ... options ... }, } ``` **DISTILL_SKIP_ADMIN_DIRS**: bool, defaults to `True` ```python DISTILL_SKIP_ADMIN_DIRS = True ``` Set `DISTILL_SKIP_ADMIN_DIRS` to `False` if you want `django-distill` to also copy over static files in the `static/admin` directory. Usually, these are not required or desired for statically generated sites. The default behaviour is to skip static admin files. # Writing single files As of `django-distill` version `3.0.0` you can use the `django_distill.renderer.render_single_file` method to write out a single file to disk using `django_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 by `django_distill` but 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: ```python # in models.py from django.db.models.signals import post_save from django.dispatch import receiver from django_distill.renderer import render_single_file @receiver(post_save, sender=SomeBlogPostModel) def write_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 for `render_single_file` is similar to Django's `url.reverse`. The full usage interface is: ```python 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: ```python # in urls.py distill_path('post/_.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts), ``` Your usage would be: ```python 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.html` into `/path/to/output/directory` as the file `/path/to/output/directory/post/123_blog-title-slug.html`. Note any required sub-directories (`/path/to/output/directory/post` in this example) will be automatically created if they don't already exist. All `django-distill` rules apply, such as URLs ending in `/` will be saved as `/index.html` to make sense for a physical file on disk. Also note that `render_single_file` can only be imported and used into an initialised Django project. # Publishing targets You 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 by `django-distill` are: **django_distill.backends.amazon_s3**: Publish to an Amazon S3 bucket. Requires the Python library `boto3` (`$ pip install django-distill[amazon]`). The bucket must already exist (use the AWS control panel). Options: ```python '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 libraries `google-api-python-client` and `google-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: ```python '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 that `JSON_CREDENTIALS` is 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. the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, attached service account, etc). **django_distill.backends.microsoft_azure_storage**: Publish to a Microsoft Azure Blob Storage container. Requires the Python library `azure-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: ```python '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 `$web` which is where `django-distill` will attempt to publish your site. # Tests There is a minimal test suite, you can run it by cloing this repository, installing the required dependancies in `requirements.txt` then execuiting: ```bash # ./run-tests.py ``` # Contributing All properly formatted and sensible pull requests, issues and comments are welcome. %package help Summary: Development documents and examples for django-distill Provides: python3-django-distill-doc %description help # django-distill `django-distill` now has a website. Read more at: ## :link: https://django-distill.com/ `django-distill` is 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-distill` as of the 1.7 release only supports Python 3. Python 2 support has been dropped. If you require Python 2 support please pin `django-distill` to version 1.6 in your requirements.txt or Pipfile. Python 3.6 or above is advised. `django-distill` extends 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-distill` iterates 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-distill` can be defined as an extension to Django to make Django projects compatible with "Jamstack"-style site architecture. `django-distill` plugs directly into the existing Django framework without the need to write custom renderers or other more verbose code. You can also integrate `django-distill` with 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-cachekiller](https://github.com/meeb/django-cachekiller) There is a complete example site that creates a static blog and uses `django-distill` with `django-cachekiller` via continuous deployment on Netlify available here: [:link: meeb/django-distill-example](https://github.com/meeb/django-distill-example) # Installation Install from pip: ```bash $ pip install django-distill ``` Add `django_distill` to your `INSTALLED_APPS` in your `settings.py`: ```python INSTALLED_APPS = [ # ... other apps here ... 'django_distill', ] ``` That's it. # Limitations `django-distill` generates static pages and therefore only views which allow `GET` requests that return an `HTTP 200` status code are supported. It is assumed you are using URI parameters such as `/blog/123-abc` and 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 the `LANGUAGE_CODE` value in your `settings.py`. Static media files such as images and style sheets are copied from your static media directory defined in `STATIC_ROOT`. This means that you will want to run `./manage.py collectstatic` **before** you run `./manage.py distill-local` if you have made changes to static media. `django-distill` doesn't chain this request by design, however you can enable it with the `--collectstatic` argument. # Usage Assuming you have an existing Django project, edit a `urls.py` to include the `distill_path` function which replaces Django's standard `path` function and supports the new keyword arguments `distill_func` and `distill_file`. The `distill_func` argument should be provided with a function or callable class that returns an iterable or `None`. The `distill_file` argument 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 `/example` to any other name like `example.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 the `distill_file` to 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: ```python # Replaces the standard django.conf.path, identical syntax from django_distill import distill_path # Views and models from a theoretical blogging app from blog.views import PostIndex, PostView, PostYear from blog.models import Post def get_index(): # The index URI path, '', contains no parameters, named or otherwise. # You can simply just return nothing here. return None def get_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. for post in Post.objects.all(): yield {'blog_id': post_id, 'blog_title': post.title} def get_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 index distill_path('', PostIndex.as_view(), name='blog-index', # Note that for paths which have no paramters # distill_func is optional distill_func=get_index, # '' is not a valid file name! override it to index.html distill_file='index.html'), # e.g. /post/123-some-post-title using named parameters distill_path('post/-.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 appended distill_path('posts-by-year//', PostYear.as_view(), name='blog-year', distill_func=get_years), ) ``` Your site will still function identically with the above changes. Internally the `distill_func` and `distill_file` parameters 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-url` you do not have to specify the `distill_func` parameter if you don't want to. As for paths with no parameters the `distill_func` always returns `None`, this is set as the default behaviour for `distill_func`s. You can use the `distill_re_path` function as well, which replaces the default `django.urls.re_path` function. Its usage is identical to the above: ```python from django_distill import distill_re_path urlpatterns = ( 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 the `distill_url` function instead which replaces the `django.conf.urls.url` or `django.urls.url` functions. Its usage is identical to the above: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_func=some_func), ) ``` ### Parameters in file names You can standard Python string formatting in `distill_file` as 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 your `path` pattern matches the `distill_file` pattern or your links might not work in Django. An example: ```python # Override file path with parameters. Values are taken from the URL pattern urlpatterns = ( distill_path('post/-.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts, distill_file="post/{blog_id}-{blog_title}.html" ) ``` ### Non-standard status codes All views rendered by `django-distill` into 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 the `distill_status_codes` optional argument to a view. For example: ```python from django_distill import distill_url urlpatterns = ( distill_url(r'some/regex' SomeView.as_view(), name='url-view', distill_status_codes=(200, 404), distill_func=some_func), ) ``` The optional `distill_status_codes` argument 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 support `django-distill` will mirror whatever your installed version of Django supports, therefore at some point the `distill_url` function will cease working in the future when Django 2.x itself depreciates the `django.conf.urls.url` and `django.urls.url` functions. You can use `distill_re_path` as a drop-in replacement. It is advisable to use `distill_path` or `distill_re_path` if you're building a new site now. # The `distill-local` command Once you have wrapped the URLs you want to generate statically you can now generate a complete functioning static site with: ```bash $ ./manage.py distill-local [optional /path/to/export/directory] ``` Under the hood this simply iterates all URLs registered with `distill_url` and 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 the `STATIC_ROOT` are copied over. Existing files with the same name are replaced in the target directory and orphan files are deleted. `distill-local` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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. **Note** If 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. # The `distill-publish` command ```bash $ ./manage.py distill-publish [optional destination here] ``` If you have configured at least one publishing destination (see below) you can use the `distill-publish` command 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 the `PUBLIC_URL`. `distill-publish` supports the following optional arguments: `--collectstatic`: Automatically run `collectstatic` on 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 to `1` thread. **Note** that this means if you use `--force` and `--quiet` that the output directory will have all files not part of the site export deleted without any confirmation. **Note** If 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. # The `distill-test-publish` command ```bash $ ./manage.py distill-test-publish [optional destination here] ``` This will connect to your publishing target, authenticate to it, upload a randomly named file, verify it exists on the `PUBLIC_URL` and then delete it again. Use this to check your publishing settings are correct. `distill-test-publish` has no arguments. # Optional configuration settings You can set the following optional `settings.py` variables: **DISTILL_DIR**: string, default directory to export to: ```python DISTILL_DIR = '/path/to/export/directory' ``` **DISTILL_PUBLISH**: dictionary, like Django's `settings.DATABASES`, supports `default`: ```python DISTILL_PUBLISH = { 'default': { ... options ... }, 'some-other-target': { ... options ... }, } ``` **DISTILL_SKIP_ADMIN_DIRS**: bool, defaults to `True` ```python DISTILL_SKIP_ADMIN_DIRS = True ``` Set `DISTILL_SKIP_ADMIN_DIRS` to `False` if you want `django-distill` to also copy over static files in the `static/admin` directory. Usually, these are not required or desired for statically generated sites. The default behaviour is to skip static admin files. # Writing single files As of `django-distill` version `3.0.0` you can use the `django_distill.renderer.render_single_file` method to write out a single file to disk using `django_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 by `django_distill` but 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: ```python # in models.py from django.db.models.signals import post_save from django.dispatch import receiver from django_distill.renderer import render_single_file @receiver(post_save, sender=SomeBlogPostModel) def write_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 for `render_single_file` is similar to Django's `url.reverse`. The full usage interface is: ```python 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: ```python # in urls.py distill_path('post/_.html', PostView.as_view(), name='blog-post', distill_func=get_all_blogposts), ``` Your usage would be: ```python 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.html` into `/path/to/output/directory` as the file `/path/to/output/directory/post/123_blog-title-slug.html`. Note any required sub-directories (`/path/to/output/directory/post` in this example) will be automatically created if they don't already exist. All `django-distill` rules apply, such as URLs ending in `/` will be saved as `/index.html` to make sense for a physical file on disk. Also note that `render_single_file` can only be imported and used into an initialised Django project. # Publishing targets You 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 by `django-distill` are: **django_distill.backends.amazon_s3**: Publish to an Amazon S3 bucket. Requires the Python library `boto3` (`$ pip install django-distill[amazon]`). The bucket must already exist (use the AWS control panel). Options: ```python '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 libraries `google-api-python-client` and `google-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: ```python '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 that `JSON_CREDENTIALS` is 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. the `GOOGLE_APPLICATION_CREDENTIALS` environment variable, attached service account, etc). **django_distill.backends.microsoft_azure_storage**: Publish to a Microsoft Azure Blob Storage container. Requires the Python library `azure-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: ```python '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 `$web` which is where `django-distill` will attempt to publish your site. # Tests There is a minimal test suite, you can run it by cloing this repository, installing the required dependancies in `requirements.txt` then execuiting: ```bash # ./run-tests.py ``` # Contributing All properly formatted and sensible pull requests, issues and comments are welcome. %prep %autosetup -n django-distill-3.1.3 %build %py3_build %install %py3_install install -d -m755 %{buildroot}/%{_pkgdocdir} if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi pushd %{buildroot} if [ -d usr/lib ]; then find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/lib64 ]; then find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/bin ]; then find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/sbin ]; then find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst fi touch doclist.lst if [ -d usr/share/man ]; then find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst fi popd mv %{buildroot}/filelist.lst . mv %{buildroot}/doclist.lst . %files -n python3-django-distill -f filelist.lst %dir %{python3_sitelib}/* %files help -f doclist.lst %{_docdir}/* %changelog * Wed May 31 2023 Python_Bot - 3.1.3-1 - Package Spec generated