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
The uncompromising Python code formatter

The Uncompromising Code Formatter “Any color you like.” Black is the uncompromising Python code formatter. By using it, you agree to cede control over

Python Software Foundation 30.7k Jan 03, 2023
Send push notifications to mobile devices through GCM or APNS in Django.

django-push-notifications A minimal Django app that implements Device models that can send messages through APNS, FCM/GCM and WNS. The app implements

Jazzband 2k Dec 26, 2022
Django Girls Tutorial Workshop

Django Girls Tutorial Workshop A log of activities during the workshop. this is an H2 git remote add origin https://github.com/ahuimanu/django_girls_t

Jeffry Babb 1 Oct 27, 2021
Projeto Crud Django and Mongo

Projeto-Crud_Django_and_Mongo Configuração para rodar o projeto Download Project

Samuel Fernandes Oliveira 2 Jan 24, 2022
A calendaring app for Django. It is now stable, Please feel free to use it now. Active development has been taken over by bartekgorny.

Django-schedule A calendaring/scheduling application, featuring: one-time and recurring events calendar exceptions (occurrences changed or cancelled)

Tony Hauber 814 Dec 26, 2022
Application made in Django to generate random passwords as based on certain criteria .

PASSWORD GENERATOR Welcome to Password Generator About The App Password Generator is an Open Source project brought to you by Iot Lab,KIIT and it brin

IoT Lab KIIT 3 Oct 21, 2021
Per object permissions for Django

django-guardian django-guardian is an implementation of per object permissions [1] on top of Django's authorization backend Documentation Online docum

3.3k Jan 04, 2023
django app that allows capture application metrics by each user individually

Django User Metrics django app that allows capture application metrics by each user individually, so after you can generate reports with aggregation o

Reiner Marquez 42 Apr 28, 2022
This is a personal django website for forum posts

Django Web Forum This is a personal django website for forum posts It includes login, registration and forum posts with date time. Tech / Framework us

5 May 12, 2022
Django + AWS Elastic Transcoder

Django Elastic Transcoder django-elastic-transcoder is an Django app, let you integrate AWS Elastic Transcoder in Django easily. What is provided in t

StreetVoice 66 Dec 14, 2022
Silk is a live profiling and inspection tool for the Django framework.

Silk is a live profiling and inspection tool for the Django framework. Silk intercepts and stores HTTP requests and database queries before presenting them in a user interface for further inspection:

Jazzband 3.7k Jan 02, 2023
A Django app for working with BTCPayServer

btcpay-django A Django app for working with BTCPayServer Installation pip install btcpay-django Developers Release To cut a release, run bumpversion,

Crawford 3 Nov 20, 2022
A blog app powered by python-django

Django_BlogApp This is a blog app powered by python-django Features Add and delete blog post View someone else blog Can add comment to that blog And o

Manish Jalui 1 Sep 12, 2022
This is a Django app that uses numerous Google APIs such as reCAPTURE, maps and waypoints

Django project that uses Googles APIs to auto populate fields, display maps and routes for multiple waypoints

Bobby Stearman 57 Dec 03, 2022
A pickled object field for Django

django-picklefield About django-picklefield provides an implementation of a pickled object field. Such fields can contain any picklable objects. The i

Gintautas Miliauskas 167 Oct 18, 2022
mirage ~ ♪ extended django admin or manage.py command.

mirage ~ ♪ extended django admin or manage.py command. ⬇️ Installation Installing Mirage with Pipenv is recommended. pipenv install -d mirage-django-l

Shota Shimazu 6 Feb 14, 2022
📝 Sticky Notes in Django admin

django-admin-sticky-notes Share notes between superusers. Installation Install via pip: pip install django_admin_sticky_notes Put django_admin_sticky_

Dariusz Choruży 7 Oct 06, 2021
Python port of Google's libphonenumber

phonenumbers Python Library This is a Python port of Google's libphonenumber library It supports Python 2.5-2.7 and Python 3.x (in the same codebase,

David Drysdale 3.1k Jan 08, 2023
Add infinite scroll to any django app.

django-infinite-scroll Add infinite scroll to any django app. Features - Allows to add infinite scroll to any page.

Gustavo Teixeira 1 Dec 26, 2021
Automatic class scheduler for Texas A&M written with Python+Django and React+Typescript

Rev Registration Description Rev Registration is an automatic class scheduler for Texas A&M, aimed at easing the process of course registration by gen

Aggie Coding Club 21 Nov 15, 2022