Django/Jinja template indenter

Related tags

Djangodjhtml
Overview

DjHTML

A pure-Python Django/Jinja template indenter without dependencies.

DjHTML is a fully automatic template indenter that works with mixed HTML/CSS/Javascript templates that contain Django or Jinja template tags. It works similar to other code-formatting tools such as Black and interoperates nicely with pre-commit.

DjHTML is an indenter and not a formatter: it will only add/remove whitespace at the beginning of lines. It will not insert newlines or other characters. The goal is to correctly indent already well-structured templates, not to fix broken ones.

For example, consider the following incorrectly indented template:

<!doctype html>
<html>
    <body>
        {% block content %}
        Hello, world!
        {% endblock %}
        <script>
            $(function() {
            console.log('Hi mom!');
            });
        </script>
    </body>
</html>

This is what it will look like after processing by DjHTML:

<!doctype html>
<html>
    <body>
        {% block content %}
            Hello, world!
        {% endblock %}
        <script>
            $(function() {
                console.log('Hi mom!');
            });
        </script>
    </body>
</html>

Installation

Install DjHTML with the following command:

$ pip install djhtml

Usage

After installation you can indent templates using the djhtml command. The default is to write the indented output to standard out. To modify the source file in-place, use the -i / --in-place option:

$ djhtml -i template.html
reindented template.html
1 template has been reindented.

Normally, the exit status of 0 means everything went well, regardless of whether any files were changed. If any errors were encountered, the exit status indicated the number of problematic files. However, when the option -c / --check is used, the exit status is the number of files that would have changed, but no changes are actually made.

All available options are:

  • -h / --help: show overview of available options
  • -i / --in-place: modify files in-place
  • -c / --check: don't modify files; the exit status is the number of files that would have changed
  • -q / --quiet: don't print any output
  • -t / --tabwidth: set tabwidth (default is 4)
  • -o / --output-file: write output to specified file

fmt:off and fmt:on

You can exclude specific lines from being processed with the {# fmt:off #} and {# fmt:on #} operators:

<div class="
    {# fmt:off #}
      ,-._|\
     /     .\
     \_,--._/
    {# fmt:on #}
"/>

Contents inside <pre> ... </pre>, <!-- ... --->, /* ... */, and {% comment %} ... {% endcomment %} tags are also ignored (depending on the current mode).

Modes

The indenter operates in one of three different modes:

  • DjHTML mode: the default mode. Invoked by using the djhtml command or the pre-commit hook.

  • DjCSS mode. Will be entered when a <style> tag is encountered in DjHTML mode. It can also be invoked directly with the command djcss.

  • DjJS mode. Will be entered when a <script> tag is encountered in DjHTML mode. It can also be invoked directly with the command djjs.

pre-commit configuration

You can use DjHTML as a pre-commit hook to automatically indent your templates upon each commit.

First, install pre-commit:

$ pip install pre-commit
$ pre-commit install

Then, add the following to your .pre-commit-config.yaml:

repos:
- repo: https://github.com/rtts/djhtml
  rev: main
  hooks:
    - id: djhtml

Finally, run the following command:

$ pre-commit autoupdate

Now when you run git commit you will see something like the following output:

$ git commit

djhtml...................................................................Failed
- hook id: djhtml
- files were modified by this hook

reindented template.html
1 template has been reindented.

To inspect the changes that were made, use git diff. If you are happy with the changes, you can commit them normally. If you are not happy, please do the following:

  1. Run SKIP=djhtml git commit to commit anyway, skipping the djhtml hook.

  2. Consider opening an issue with the relevant part of the input file that was incorrectly formatted, and an example of how it should have been formatted.

Your feedback for improving DjHTML is very welcome!

Development

Use your preferred system for setting up a virtualenv, docker environment, or whatever else, then run the following:

python -m pip install -e .[dev]
pre-commit install --install-hooks

Tests can then be run quickly in that environment:

python -m unittest discover -v

Or testing in all available supported environments and linting can be run with nox:

nox
Comments
  • (S)CSS files get no indention

    (S)CSS files get no indention

    Hi there, just checked out this packge after the DjangoCon recommendation. It looks very nice but I guess I found an issue.

    The djcss is removing any indention of my SCSS files which makes it more or less unreadable.

    grafik

    Any ideas how I can fix this? Is there a setting to adjust the indention?

    Thx!

    cannot-reproduce 
    opened by GitRon 21
  • Too many open files when trying to edit multiple files

    Too many open files when trying to edit multiple files

    Hi,

    Thanks for creating this tool. I wanted to update all the templates of my project like this:

    find . -name "*.html" -type f -exec djhtml -i -t2 {} +, But it gives me this error:

    But I get this error:

    usage: djhtml [-h] [-i] [-q] [-t N] [-o filename] [filenames ...]
    djhtml: error: argument filenames: can't open './demo_cms/template/content_news.html': [Errno 24] Too many open files: './demo_cms/template/content_news.html'
    

    Please let me know if I am doing something wrong. I don't want to edit each file by hand.

    opened by vinitkumar 7
  • Config file to ignore some block tags

    Config file to ignore some block tags

    Hello. Firstly thanks for the library, we're trying it out on our projects and really like it so far.

    We have one small problem though. We use a markdown block filter, where the content of the block is parsed as markdown. This means initial space (as djhtml adds) is parsed as a blockquote.

    I know we can use the fmt control comments, but ideally we'd be able to automatically ignore anything inside {% filter markdown %}.

    I was going to raise a PR for this, but I realise that this is a very specific case. Rather than adding exemptions ad-hoc, it might be better to allow configuring blocks to be excluded by the user / project.

    Alternatively, it might be a good idea to slip formatting inside filter blocks altogether - but I don't know enough about how they're used in other projects to know if this would be a good idea.

    I'd be interested to hear your thoughts on this, and would be happy to submit a PR once we've worked out a good solution.

    opened by symroe 6
  • Could you also support jinja templates?

    Could you also support jinja templates?

    Thanks for building this, its been on my wish list for a while.

    I think it wouldn't be too hard to support Jinja2 templates as well (I think the syntax is very similar), would this be something you are willing to do?

    yes 
    opened by pgjones 6
  • Still pretty verbose when running `pre-commit run --all-files`

    Still pretty verbose when running `pre-commit run --all-files`

    When running pre-commit run --all-files it's still pretty verbose:

    djhtml...................................................................Failed
    - hook id: djhtml
    - files were modified by this hook
    
    All done! \o/
    16 templates left unchanged.
    All done! \o/
    16 templates left unchanged.
    All done! \o/
    16 templates left unchanged.
    All done! \o/
    16 templates left unchanged.
    All done! \o/
    16 templates left unchanged.
    All done! \o/
    16 templates left unchanged.
    reindented babeldat/core/templates/partials/_menu.html
    All done! \o/
    1 template reindented, 15 templates left unchanged.
    All done! \o/
    11 templates left unchanged.
    

    FYI: I had 16 files staged, only 4 of them were templates

    opened by richarddewit 5
  • In/dedentation is sometimes incorrect when multiple tags are on the same line

    In/dedentation is sometimes incorrect when multiple tags are on the same line

    Really impressed with this package!

    I noticed that in some of my longer templates in which I have sloppily at times had variations on <div><div> or </div><div> or </div></div> in the same line, things can get a bit weird. It's still valid html, but it's not pretty, and seems to trip djhtml up just a bit.

    For instance, this minimal example:

    {% block css %}
    {% endblock css %}{% block content %}
    <div class="row">
    <div class="input-group date" data-provide="datepicker" data-date-format="yyyy/mm/dd">
    <div class="btn">
    </div></div>
    </div><div>
    <div class="custom-control custom-switch">
    <input type="checkbox" name="filter-changes" id="filter-changes" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_changes %}{% if request.session.manage_filter_changes == True %}checked{% endif %}{% endif %}>
    </div>
    <form class="form-inline">
    <div class="custom-control custom-switch">
    <input type="checkbox" name="filter-status-new" id="filter-status-new" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_status_new %}{% if request.session.manage_filter_status_new == True %}checked{% endif %}{% endif %}>
    </div>
    </form>
    </div>
    {% endblock content %}
    

    I would expect something like this where the final div and {% endblock %} are fully left-justified:

    {% block css %}
    {% endblock css %}{% block content %}
    <div class="row">
        <div class="input-group date" data-provide="datepicker" data-date-format="yyyy/mm/dd">
            <div class="btn">
        </div></div>
    </div><div>
        <div class="custom-control custom-switch">
            <input type="checkbox" name="filter-changes" id="filter-changes" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_changes %}{% if request.session.manage_filter_changes == True %}checked{% endif %}{% endif %}>
        </div>
        <form class="form-inline">
            <div class="custom-control custom-switch">
                <input type="checkbox" name="filter-status-new" id="filter-status-new" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_status_new %}{% if request.session.manage_filter_status_new == True %}checked{% endif %}{% endif %}>
            </div>
        </form>
    </div>
    {% endblock content %}
    

    Or maybe this in a perfect world (though I suspect this would take a LOT more work to achieve via djhtml)

    {% block css %}
    {% endblock css %}
    {% block content %}
    <div class="row">
        <div class="input-group date" data-provide="datepicker" data-date-format="yyyy/mm/dd">
            <div class="btn">
            </div>
        </div>
    </div>
    <div>
        <div class="custom-control custom-switch">
            <input type="checkbox" name="filter-changes" id="filter-changes" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_changes %}{% if request.session.manage_filter_changes == True %}checked{% endif %}{% endif %}>
        </div>
        <form class="form-inline">
            <div class="custom-control custom-switch">
                <input type="checkbox" name="filter-status-new" id="filter-status-new" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_status_new %}{% if request.session.manage_filter_status_new == True %}checked{% endif %}{% endif %}>
            </div>
        </form>
    </div>
    {% endblock content %}
    

    But the actual output is this:

    {% block css %}
    {% endblock css %}{% block content %}
    <div class="row">
        <div class="input-group date" data-provide="datepicker" data-date-format="yyyy/mm/dd">
            <div class="btn">
        </div></div>
    </div><div>
        <div class="custom-control custom-switch">
            <input type="checkbox" name="filter-changes" id="filter-changes" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_changes %}{% if request.session.manage_filter_changes == True %}checked{% endif %}{% endif %}>
            </div>
            <form class="form-inline">
                <div class="custom-control custom-switch">
                    <input type="checkbox" name="filter-status-new" id="filter-status-new" class="custom-control-input fs-nano" value="0" {% if request.session.manage_filter_status_new %}{% if request.session.manage_filter_status_new == True %}checked{% endif %}{% endif %}>
                    </div>
                </form>
            </div>
        {% endblock content %}
    

    I wish I were good enough with parsing/formatting to submit a PR, but hopefully this is at least helpful input.

    bug 
    opened by jacklinke 5
  • Indention bug with Stimulus action

    Indention bug with Stimulus action

    If we have a simple test.html file

    <div>
      <div data-action="click->modal#closeBackground">
      </div>
    </div>
    

    If I run djhtml -i test.html

    I got

    <div>
        <div data-action="click->modal#closeBackground">
        </div>
        </div>
    

    As you can see, the last </div> indention is not correct.

    The click->modal#closeBackground caused this problem.

    opened by michael-yin 4
  • Inserting final newlines isn’t safe

    Inserting final newlines isn’t safe

    I’ve been trying out djhtml on a project and our visual regression testing showed there were differences before-after the reformatting. Upon further inspection this seems to be due to djhtml inserting final newlines in some occurences.

    Looking at the code, this seems to be done on purpose, but there are two issues:

    • Whitespace is often sensitive in HTML – so inserting whitespace where there wasn’t any will change how the page renders.
    • The "insertion of final newline" logic isn’t consistent – djhtml will only add the newline when it made other changes to the template. It won’t add the newline if the template’s formatting is already good.

    Here is a practical example where this happens. With an icon.html template:

    <svg>
    <use href="#my-icon"></use>
    </svg>
    

    And a button.html:

    <button>{% include "icon.html" }My button</button>
    

    Without a final newline in icon.html, this renders as:

    <button<svg>
    <use href="#my-icon"></use>
    </svg>My button</button>
    

    With a final newline (which djhtml inserts) in icon.html, this becomes:

    <button<svg>
        <use href="#my-icon"></use>
    </svg>
    My button</button>
    

    This will cause additional space between the icon and the text. I’ve made a small demo to help illustrate.


    As a resolution to this, my preference would be for djhtml to completely stop inserting final newlines. There are a lot of tools that do this already for people who want to, and it’s not something that can be applied to existing HTML templates with full certainty as the behavior depends on how the partials are used.

    opened by thibaudcolas 4
  • Jinja

    Jinja "whitespace control" is not understood

    Jinja allows one to control whitespace generated by tags. For instance:

    <div>
        {% for item in sequence %}
            {{ item }}
        {%- endfor %}
    </div>
    

    but DjHTML then misses the endfor.

    opened by sjoerdjob 4
  • Support passing directories as command-line arguments

    Support passing directories as command-line arguments

    This makes it easier to run djhtml over a bunch of files in a directory or directories.

    The default suffixes are used as a filter with no configuration allowed (as black seems to work).

    I'm aware of #13 recommending using find ... | xargs or the usage of pre-commit. However, I would like a single command that works on Linux, MacOS, and crucially Windows systems (find and xargs don't work). This would allow me to recommend to users how to use djhtml without having to add lots of additional text explaining how to do it on different systems i.e. djhtml src/templates is all that is needed.

    opened by pgjones 3
  • Indentation of Internet Explorer conditional comment

    Indentation of Internet Explorer conditional comment

    The following HTML:

    <div>
    <!--[if lt IE 9]>
        <p>Not supported</p>
    <![endif]-->
    </div>
    

    gets indented as:

    <div>
        <!--[if lt IE 9]>
        <p>Not supported</p>
    <![endif]-->
    </div>
    

    I would have expected djhtml to apply the same amount of indentation to every line within the div. I imagine it’s IE conditional comments that are causing this – if so they probably don’t warrant any kind of support as they’re well past their expiration date. But I thought I’d report it anyway just in case.

    wontfix 
    opened by thibaudcolas 3
  • Indentation of multiline method chains in script tags

    Indentation of multiline method chains in script tags

    The following JS within HTML:

    <script>
    window.fetch('/test.html')
        .then((html) => {
            document.body.innerHTML = html;
        });
    </script>
    

    gets indented as:

    <script>
        window.fetch('/test.html')
            .then((html) => {
            document.body.innerHTML = html;
        });
    </script>
    

    I would have expected everything within the script tag to get one extra level of indentation. I’m not sure what is causing this, but I at least tried with both a function and the arrow function as above, so I suspect it’s the method chaining that might be the problem.

    help wanted 
    opened by thibaudcolas 1
  • Multi-line tag attribute alignment

    Multi-line tag attribute alignment

    I had some multi-line tags like:

    <img src="/some/long/path"
         alt="Some text">
    

    DjHTML indented them like so:

    <img src="/some/long/path"
      alt="Some text">
    

    I think it would be more nautral to retain the “attribute alignment”, and keep alt at the same column as src.

    Do you think this would be possible?

    Otherwise, the attribue-aligned style I found that works with DjHTML is:

    <img
      src="/some/long/path"
      alt="Some text">
    
    help wanted 
    opened by adamchainz 3
Releases(v1.5.2)
  • v1.5.2(Aug 4, 2022)

    What's Changed

    • Support passing directories as command-line arguments by @pgjones in https://github.com/rtts/djhtml/pull/64

    New Contributors

    • @pgjones made their first contribution in https://github.com/rtts/djhtml/pull/64

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.5.1...v1.5.2

    Source code(tar.gz)
    Source code(zip)
  • v1.5.1(Jun 23, 2022)

    What's Changed

    • Upgrade Black by @rupertbaker in https://github.com/rtts/djhtml/pull/63
    • Fix interpretation of closing arrows in HTML attrs by @rupertbaker in https://github.com/rtts/djhtml/pull/62

    New Contributors

    • @rupertbaker made their first contribution in https://github.com/rtts/djhtml/pull/63

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.5.0...v1.5.1

    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Feb 14, 2022)

    What's Changed

    The behavior regarding final newlines has changed between DjHTML v1.4.14 and v1.5.0. It used to always append the final newline, but now this will only happen when the source file already contains a final newline.

    See https://github.com/rtts/djhtml/issues/56 for the discussion that led to this change.

    If you still need the old behavior, the best option is to use https://github.com/pre-commit/pre-commit-hooks#end-of-file-fixer by adding the following to your .pre-commit-config.yaml:

    - repo: https://github.com/pre-commit/pre-commit-hooks
      rev: main  # replace with the latest tag on GitHub
      hooks:
        - id: end-of-file-fixer
    

    New Contributors

    • @thibaudcolas pointed out the inconsistent behavior in appending final newlines

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.4.14...v1.5.0

    Source code(tar.gz)
    Source code(zip)
  • v1.4.14(Feb 8, 2022)

    What's Changed

    • Improve pre-commit hook of DjCSS by @DmytroLitvinov in https://github.com/rtts/djhtml/pull/55

    New Contributors

    • @DmytroLitvinov made their first contribution in https://github.com/rtts/djhtml/pull/55

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.4.13...v1.4.14

    Source code(tar.gz)
    Source code(zip)
  • v1.4.13(Feb 6, 2022)

    What's Changed

    • Indent CSS functions by @neruson in https://github.com/rtts/djhtml/pull/52
    • Further indent HTML attribute values by @JaapJoris in https://github.com/rtts/djhtml/pull/54

    New Contributors

    • @neruson made their first contribution in https://github.com/rtts/djhtml/pull/52

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.4.12...v1.4.13

    Source code(tar.gz)
    Source code(zip)
  • v1.4.12(Feb 4, 2022)

  • v1.4.11(Dec 20, 2021)

    What's Changed

    • Update pre-commit hooks by @adamchainz in https://github.com/rtts/djhtml/pull/45
    • Remove linting from nox by @adamchainz in https://github.com/rtts/djhtml/pull/46
    • Add CI run of nox on GitHub Actions by @adamchainz in https://github.com/rtts/djhtml/pull/47
    • Recommend using tags for pre-commit by @adamchainz in https://github.com/rtts/djhtml/pull/44
    • Added support for JavaScript switch statement. by @pawelpel in https://github.com/rtts/djhtml/pull/49

    New Contributors

    • @pawelpel made their first contribution in https://github.com/rtts/djhtml/pull/49

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.4.10...v1.4.11

    Source code(tar.gz)
    Source code(zip)
  • v1.4.10(Nov 9, 2021)

    What's Changed

    • Support Python 3.10 by @adamchainz in https://github.com/rtts/djhtml/pull/43

    New Contributors

    • @adamchainz made their first contribution in https://github.com/rtts/djhtml/pull/43

    Full Changelog: https://github.com/rtts/djhtml/compare/v1.4.9...v1.4.10

    Source code(tar.gz)
    Source code(zip)
  • v1.4.9(Jun 16, 2021)

  • v1.4.8(Jun 7, 2021)

  • v1.4.7(Jun 4, 2021)

  • v1.4.6(Jun 3, 2021)

  • v1.4.5(May 28, 2021)

  • v1.4.4(May 28, 2021)

  • v1.4.3(May 26, 2021)

  • v1.4.2(May 25, 2021)

  • v1.4.1(May 25, 2021)

  • v1.4.0(May 23, 2021)

  • v1.3.0(May 23, 2021)

  • v1.2.2(May 18, 2021)

  • v1.2.1(May 17, 2021)

  • v1.2.0(May 17, 2021)

  • v1.0.2(May 16, 2021)

Owner
Return to the Source
Smart online platforms for innovative businesses + fun side projects
Return to the Source
django CMS Association 1.6k Jan 06, 2023
Wrapping Raml around Django rest-api's

Ramlwrap is a toolkit for Django which allows a combination of rapid server prototyping as well as enforcement of API definition from the RAML api. R

Jmons 8 Dec 27, 2021
A simple Django dev environment setup with docker for demo purposes for GalsenDev community

GalsenDEV Docker Demo This is a basic Django dev environment setup with docker and docker-compose for a GalsenDev Meetup. The main purposes was to mak

3 Jul 03, 2021
Django Pickled Model

Django Pickled Model Django pickled model provides you a model with dynamic data types. a field can store any value in any type. You can store Integer

Amir 3 Sep 14, 2022
This repository contains django library management system project.

Library Management System Django ** INSTALLATION** First of all install python on your system. Then run pip install -r requirements.txt to required se

whoisdinanath 1 Dec 26, 2022
Reusable, generic mixins for Django

django-braces Mixins for Django's class-based views. Documentation Read The Docs Installation Install from PyPI with pip: pip install django-braces Bu

Brack3t 1.9k Jan 05, 2023
I managed to attach the Django Framework to my Telegram Bot and set a webhook

I managed to attach the Django Framework to my Telegram Bot and set a webhook. I've been developing it from 10th of November 2021 and I want to have a basic working prototype.

Valentyn Vovchak 2 Sep 08, 2022
This is a repository for collecting global custom management extensions for the Django Framework.

Django Extensions Django Extensions is a collection of custom extensions for the Django Framework. Getting Started The easiest way to figure out what

Django Extensions 6k Dec 26, 2022
Django-discord-bot - Framework for creating Discord bots using Django

django-discord-bot Framework for creating Discord bots using Django Uses ASGI fo

Jamie Bliss 1 Mar 04, 2022
Inject an ID into every log message from a Django request. ASGI compatible, integrates with Sentry, and works with Celery

Django GUID Now with ASGI support! Django GUID attaches a unique correlation ID/request ID to all your log outputs for every request. In other words,

snok 300 Dec 29, 2022
Django application and library for importing and exporting data with admin integration.

django-import-export django-import-export is a Django application and library for importing and exporting data with included admin integration. Featur

2.6k Dec 26, 2022
Backend with Django .

BackendCode - Cookies Documentation: https://docs.djangoproject.com/fr/3.2/intro/ By @tcotidiane33 & @yaya Models Premium class Pack(models.Model): n

just to do it 1 Jan 28, 2022
Generate generic activity streams from the actions on your site. Users can follow any actors' activities for personalized streams.

Django Activity Stream What is Django Activity Stream? Django Activity Stream is a way of creating activities generated by the actions on your site. I

Justin Quick 2.1k Dec 29, 2022
A Django Demo Project of Students Management System

Django_StudentMS A Django Demo Project of Students Management System. From NWPU Seddon for DB Class Pre. Seddon simplify the code in 2021/10/17. Hope

2 Dec 08, 2021
Sampling profiler for Python programs

py-spy: Sampling profiler for Python programs py-spy is a sampling profiler for Python programs. It lets you visualize what your Python program is spe

Ben Frederickson 9.5k Jan 01, 2023
Bringing together django, django rest framework, and htmx

This is Just an Idea There is no code, this README just represents an idea for a minimal library that, as of now, does not exist. django-htmx-rest A l

Jack DeVries 5 Nov 24, 2022
DRF_commands is a Django package that helps you to create django rest framework endpoints faster using manage.py.

DRF_commands is a Django package that helps you to create django rest framework endpoints faster using manage.py.

Mokrani Yacine 2 Sep 28, 2022
🔃 A simple implementation of STOMP with Django

Django Stomp A simple implementation of STOMP with Django. In theory it can work with any broker which supports STOMP with none or minor adjustments.

Juntos Somos Mais 32 Nov 08, 2022
Built from scratch to replicate some of the Django admin functionality and add some more, to serve as an introspective interface for Django and Mongo.

django-mongonaut Info: An introspective interface for Django and MongoDB. Version: 0.2.21 Maintainer: Jazzband (jazzband.co) This Project is Being Mov

Jazzband 238 Dec 26, 2022
Hello world written in Django.

Learning Django 💡 create a virtual environment create python -m venv ./venv. this virtualenv file will be excluded by .gitignore activate the virtual

Dipak giri 4 Nov 26, 2021