Skip to main content

Use Django's template engine to render static files at deployment time. Extend Django's url reverse mechanism to JavaScript.

Project description

MIT license PyPI version fury.io PyPI pyversions PyPI status Documentation Status Code Cov Test Status

django-render-static

Use Django’s dynamic templates to render static files. That is, files that are collected during the collectstatic routine and likely served above Django on the stack. Static templates should be rendered preceding any run of collectstatic. Files rendered by django-render-static are immediately available to participate in the normal static file pipeline.

For example, a frequently occurring pattern that violates the DRY principle is the presence of defines, or enum like structures in server side Python code that are simply replicated in client side JavaScript. Single-sourcing these structures by generating client side code from the server side code keeps the stack bone DRY.

django-render-static includes builtins for:
  • Replicating Django’s reverse function in JavaScript (urls_to_js)

  • Auto-translating basic Python class and module structures into JavaScript (modules_to_js, classes_to_js)

You can report bugs and discuss features on the issues page.

Contributions are encouraged! Especially additional template tags and filters!

Full documentation at read the docs.

Installation

  1. Clone django-render-static from GitHub or install a release off PyPI :

pip install django-render-static
  1. Add ‘render_static’ to your INSTALLED_APPS :

INSTALLED_APPS = [
    'render_static',
]
  1. Add a STATIC_TEMPLATES configuration directive to your settings file:

STATIC_TEMPLATES = {
    'templates' : {
        'path/to/template': {
            'context' { 'variable': 'value' }
        }
}
  1. Run renderstatic preceding every run of collectstatic :

$> manage.py renderstatic
$> manage.py collectstatic

Usage

Generating Javascript Defines

You have an app with a model with a character field that has several valid choices defined in an enumeration type way, and you’d like to export those defines to JavaScript. You’d like to include a template for other’s using your app to use to generate a defines.js file. Say your app structure looks like this:

.
└── my_app
    ├── __init__.py
    ├── apps.py
    ├── defines.py
    ├── models.py
    ├── static_templates
    │   └── my_app
    │       └── defines.js
    └── urls.py

Your defines/model classes might look like this:

class Defines:

    DEFINE1 = 'D1'
    DEFINE2 = 'D2'
    DEFINE3 = 'D3'
    DEFINES = (
        (DEFINE1, 'Define 1'),
        (DEFINE2, 'Define 2'),
        (DEFINE3, 'Define 3')
    )

class MyModel(Defines, models.Model):

    define_field = models.CharField(choices=Defines.DEFINES, max_length=2)

And your defines.js template might look like this:

var defines = {
    {{ "my_app.defines.Defines"|split|classes_to_js }}
};

If someone wanted to use your defines template to generate a JavaScript version of your Python class their settings file might look like this:

STATIC_TEMPLATES = {
    'templates': {
        'my_app/defines.js': {}
    }
}

And then of course they would call renderstatic before collectstatic:

$> ./manage.py renderstatic
$> ./manage.py collectstatic

This would create the following file:

.
└── my_app
    └── static
        └── my_app
            └── defines.js

Which would look like this:

var defines = {
    Defines: {
        DEFINE1: 'D1'
        DEFINE2: 'D2'
        DEFINE3: 'D3'
        DEFINES: [
            ['D1', 'Define 1'],
            ['D2', 'Define 2'],
            ['D3', 'Define 3']
        ]
    }
};

URL reverse functions

You’d like to be able to call something like reverse on path names from your client JavaScript code the same way you do from Python Django code. You don’t want to expose your admin paths though.

Your settings file might look like:

from pathlib import Path

BASE_DIR = Path(__file__).parent

STATICFILES_DIRS = [
    BASE_DIR / 'more_static'
]

STATIC_TEMPLATES = {
    'ENGINES': [{
        'BACKEND': 'render_static.backends.StaticDjangoTemplates',
        'OPTIONS': {
            'loaders': [
                ('render_static.loaders.StaticLocMemLoader', {
                    'urls.js': (
                        '{% urls_to_js visitor="render_static.ClassURLWriter" '
                        'exclude=exclude %}'
                    )
                })
             ],
            'builtins': ['render_static.templatetags.render_static']
        },
    }],
    'templates': {
        'urls.js': {
            'dest': BASE_DIR / 'more_static' / 'urls.js',
            'context': {
                'exclude': ['admin']
            }
        }
    }
}

Then call renderstatic before collectstatic:

$> ./manage.py renderstatic
$> ./manage.py collectstatic

If your root urls.py looks like this:

from django.contrib import admin
from django.urls import include, path

from .views import MyView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('simple', MyView.as_view(), name='simple'),
    path('simple/<int:arg1>', MyView.as_view(), name='simple'),
    path('different/<int:arg1>/<str:arg2>', MyView.as_view(), name='different'),
]

Then urls.js will look like this:

class URLResolver {

    match(kwargs, args, expected) {
        if (Array.isArray(expected)) {
            return Object.keys(kwargs).length === expected.length &&
                expected.every(value => kwargs.hasOwnProperty(value));
        } else if (expected) {
            return args.length === expected;
        } else {
            return Object.keys(kwargs).length === 0 && args.length === 0;
        }
    }

    reverse(qname, kwargs={}, args=[]) {
        let url = this.urls;
        for (const ns of qname.split(':')) {
            if (ns && url) { url = url.hasOwnProperty(ns) ? url[ns] : null; }
        }
        if (url) {
            let pth = url(kwargs, args);
            if (typeof pth === "string") { return pth; }
        }
        throw new TypeError(`No reversal available for parameters at path: ${qname}`);
    }

    urls = {
        "simple": (kwargs={}, args=[]) => {
            if (this.match(kwargs, args)) { return "/simple/"; }
            if (this.match(kwargs, args, ['arg1'])) { return `/simple/${kwargs["arg1"]}`; }
        },
        "different": (kwargs={}, args=[]) => {
            if (this.match(kwargs, args, ['arg1','arg2'])) {
                return `/different/${kwargs["arg1"]}/${kwargs["arg2"]}`;
            }
        },
    }
};

So you can now fetch paths like this:

// /different/143/emma
const urls = new URLResolver();
urls.reverse('different', {'arg1': 143, 'arg2': 'emma'});

// reverse also supports query parameters
// /different/143/emma?intarg=0&listarg=A&listarg=B&listarg=C
url.reverse(
    'different',
    {
        kwargs: {arg1: 143, arg2: 'emma'},
        query: {
            intarg: 0,
            listarg: ['A', 'B', 'C']
        }
    }
);

URLGenerationFailed Exceptions & Placeholders

If you encounter a URLGenerationFailed exception, not to worry. You most likely need to register a placeholder for the argument in question. A placeholder is just a string or object that can be coerced to a string that matches the regular expression for the argument:

from render_static.placeholders import register_variable_placeholder

app_name = 'year_app'
urlpatterns = [
    re_path(r'^fetch/(?P<year>\d{4})/$', YearView.as_view(), name='fetch_year')
]

register_variable_placeholder('year', 2000, app_name=app_name)

django-render-static avoids overly complex string parsing logic by reversing the urls and using the resultant regular expression match objects to determine where argument substitutions are made. This keeps the code simple, reliable and avoids deep dependencies on Django’s url configuration code. Placeholders are the price paid for that reliability. Common default placeholders are attempted after all registered placeholders fail, and all of Django’s native path converters are supported. This should allow most urls to work out of the box.

Users are strongly encouraged to use path instead of re_path and register their own custom converters when needed. Placeholders can be directly registered on the converter (and are then conveniently available to users of your app!):

from django.urls.converters import register_converter

class YearConverter:
    regex = '[0-9]{4}'
    placeholder = 2000  # this attribute is used by `url_to_js` to reverse paths

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return str(value)


register_converter(YearConverter, 'year')

urlpatterns = [
    path('fetch/<year:year>', YearView.as_view(), name='fetch_year')
]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-render-static-1.1.3.tar.gz (40.2 kB view details)

Uploaded Source

Built Distribution

django_render_static-1.1.3-py3-none-any.whl (43.5 kB view details)

Uploaded Python 3

File details

Details for the file django-render-static-1.1.3.tar.gz.

File metadata

  • Download URL: django-render-static-1.1.3.tar.gz
  • Upload date:
  • Size: 40.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.12 CPython/3.10.1 Darwin/20.6.0

File hashes

Hashes for django-render-static-1.1.3.tar.gz
Algorithm Hash digest
SHA256 cfc4cbcc91749687030bb6dd56e3c56584bf04e26d1ad6a5eb075b6e87751380
MD5 ddce18cad8b6371d6dc79e3d6e945994
BLAKE2b-256 d6a297423ba05c6c3d0868cea9376d8e922a81274be4701cbb1a0f4bf75c8a06

See more details on using hashes here.

File details

Details for the file django_render_static-1.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for django_render_static-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a4768a78d30b2edb0e1f3b33e28abd750f463209f6fe529cc42ac9f4e6771a5d
MD5 5b30dbb15f9d81b9b58e1a775ea318df
BLAKE2b-256 0632f197a4f0cac86f81d1445ea7b150fcf103e97b1e21bef3d525c6acc3225c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page