How to Highlight Code Syntax – Django | Lookaway Information

Documented by Kyle Bruder on Jul 10, 2021


Screenshot_from_2021-06-11_20-02-18

Introduction

In order to show highlighted code content on your Django website, let us take advantage of existing code that people have made available. In this approach we will use the Python module “Pygments”, a nice solarized theme by Ethan Schoonover and the Code model from Lookaway CMS as an example. "Code" is a simple model with some meta data and a TextField that contains the code. With Pygments and theme handling the rendering for us and our Code model providing the text to be rendered, the question is, “when and where to render?”.

There are many ways to do this in Django, but we are going to create a custom template filter that will take a given string and return HTML that will render the highlighted text. This operation will be done every time the template itself is rendered, which is kind of a bummer, but this is kind of what Django is doing all the time anyway.


  1. The Model

    This example is straight out of the Lookaway CMS code. The Objects App features a Code model. The model has a user-supplied, optional “file_path” field which we will attempt to use in order to determine which lexer to use. If the model instance does not have a “file_path” then we will use Pygments’ guess_lexer() method.

    objects/models.py
    class Code(MetaDataMixin, MarshmallowMixin):
    
        title = models.CharField(
            max_length=256,
        )
        text = models.TextField(
            max_length=1024,
            blank = True,
            null = True,
        )
        code = models.TextField(
            max_length=65535,
        )
        language = models.CharField(
            max_length=64,
        )
        language_version = models.CharField(
            max_length=64,
        )
        file_path = models.CharField(
            max_length=256,
            blank=True,
            null=True,
        )
        source = models.CharField(
            max_length=64,
            blank=True,
            null=True,
        )
        source_url = models.URLField(
            max_length=256,
            blank=True,
            null=True,
        )
        md5 = models.CharField(
            max_length=32,
        )
    
        class Meta:
            ordering = ['order', 'title', 'language',]
    
        def __str__(self):
            return '{} - {} {}'.format(self.title, self.language, self.language_version,)
    
        def get_absolute_url(self):
            return reverse('objects:code_detail', kwargs={'pk': self.pk,})
    
        def get_md5(string):
            '''
            Given a string, returns a MD5 hash digest
            '''
            md5 = hashlib.md5(string.encode())
            digest = md5.hexdigest()
            return digest
    

    Source: Kbruder Tech

  2. Add a Custom Template Filter

    Ensure your app has a directory contains a “templatetags” directory that, in turn, contains the empty “__init__.py” file. Create or edit a custom filter python file in “templatetags” as well. Add the following lines in the filter file.

    objects/templatetags/custom_filters.py
    from django import template
    
    register = template.Library()
    
    ## Objects
    
    @register.filter(needs_autoescape=True)
    def highlight_syntax(code, path, autoescape=True):
        from pygments import highlight
        from pygments.lexers import guess_lexer, get_lexer_for_filename
        from pygments.formatters import HtmlFormatter
        from django.utils.safestring import mark_safe
    
        if path:
            try:
                lexer = get_lexer_for_filename(path)
            except:
                lexer = guess_lexer(code)
        else:
            lexer = guess_lexer(code)
        formatter = HtmlFormatter(cssclass="highlight code-block", style="friendly")
        return mark_safe(highlight(code, lexer, formatter))
    

    Source: Kbruder Tech

    objects/templatetags/
    ├── custom_filters.py
    ├── __init__.py
    

    You app directory should resemble something like this.

  3. Resolve CSS Conflicts (Bootstrap)

    If you are using Bootstrap, add the following CSS lines to your styles to resolve an issue with font coloring in <pre> tags inheriting in a way that causes the text to become unreadable. This issue took me a long time to nail down, but I did find a great article that sorted me out.

    static/styles.css
    /* Thanks to this site for solving the Bootstrap/Pygments conflict */
    /* https://alcidanalytics.com/p/pygments-bootstrap-css-conflict */
    /* fix bootstrap conflict with Pygments */
    .highlight pre { white-space: pre;
                      border-radius: inherit;
                      display: inherit;
                      background-color: inherit;
                      border: inherit;
                      color: inherit;
                    }
    
    /* fix conflict with other sytlesheets */
    .highlight .m { margin: inherit; }
    

    Source: Alcid Analyitcs

  4. Add a Theme

    Include the following lines with your CSS. There are many other themes available if this one doesn’t work with your existing style. Also, you can mess around with changing these colors to create your own theme.

    static/styles.css
    /* Solarized Dark 
    For use with Jekyll and Pygments
    http://ethanschoonover.com/solarized
    SOLARIZED HEX      ROLE
    --------- -------- ------------------------------------------
    base03    #002b36  background
    base01    #586e75  comments / secondary content
    base1     #93a1a1  body text / default code / primary content
    orange    #cb4b16  constants
    red       #dc322f  regex, special keywords
    blue      #268bd2  reserved keywords
    cyan      #2aa198  strings, numbers
    green     #859900  operators, other keywords
    */
    .highlight { background-color: #002b36; color: #93a1a1 }
    .highlight .c { color: #586e75 } /* Comment */
    .highlight .err { color: #93a1a1 } /* Error */
    .highlight .g { color: #93a1a1 } /* Generic */
    .highlight .k { color: #859900 } /* Keyword */
    .highlight .l { color: #93a1a1 } /* Literal */
    .highlight .n { color: #93a1a1 } /* Name */
    .highlight .o { color: #859900 } /* Operator */
    .highlight .x { color: #cb4b16 } /* Other */
    .highlight .p { color: #93a1a1 } /* Punctuation */
    .highlight .cm { color: #586e75 } /* Comment.Multiline */
    .highlight .cp { color: #859900 } /* Comment.Preproc */
    .highlight .c1 { color: #586e75 } /* Comment.Single */
    .highlight .cs { color: #859900 } /* Comment.Special */
    .highlight .gd { color: #2aa198 } /* Generic.Deleted */
    .highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
    .highlight .gr { color: #dc322f } /* Generic.Error */
    .highlight .gh { color: #cb4b16 } /* Generic.Heading */
    .highlight .gi { color: #859900 } /* Generic.Inserted */
    .highlight .go { color: #93a1a1 } /* Generic.Output */
    .highlight .gp { color: #93a1a1 } /* Generic.Prompt */
    .highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
    .highlight .gu { color: #cb4b16 } /* Generic.Subheading */
    .highlight .gt { color: #93a1a1 } /* Generic.Traceback */
    .highlight .kc { color: #cb4b16 } /* Keyword.Constant */
    .highlight .kd { color: #268bd2 } /* Keyword.Declaration */
    .highlight .kn { color: #859900 } /* Keyword.Namespace */
    .highlight .kp { color: #859900 } /* Keyword.Pseudo */
    .highlight .kr { color: #268bd2 } /* Keyword.Reserved */
    .highlight .kt { color: #dc322f } /* Keyword.Type */
    .highlight .ld { color: #93a1a1 } /* Literal.Date */
    .highlight .m { color: #2aa198 } /* Literal.Number */
    .highlight .s { color: #2aa198 } /* Literal.String */
    .highlight .na { color: #93a1a1 } /* Name.Attribute */
    .highlight .nb { color: #B58900 } /* Name.Builtin */
    .highlight .nc { color: #268bd2 } /* Name.Class */
    .highlight .no { color: #cb4b16 } /* Name.Constant */
    .highlight .nd { color: #268bd2 } /* Name.Decorator */
    .highlight .ni { color: #cb4b16 } /* Name.Entity */
    .highlight .ne { color: #cb4b16 } /* Name.Exception */
    .highlight .nf { color: #268bd2 } /* Name.Function */
    .highlight .nl { color: #93a1a1 } /* Name.Label */
    .highlight .nn { color: #93a1a1 } /* Name.Namespace */
    .highlight .nx { color: #93a1a1 } /* Name.Other */
    .highlight .py { color: #93a1a1 } /* Name.Property */
    

    Source: Ethan Schoonover

  5. Load and Apply the Filter in Your Template

    The template filter needs to be loaded before it is used. Pygments will render the code inside of a <div>, so in most cases you can drop the filtered string right on the page.

    Tip: It may be necessary to restart the server in order to load the new filters.

    objects/templates/objects/code_detail.html
    ...
    {% load custom_filters %}
    {{ code.code|highlight_syntax:code.file_path|safe }}
    ...
    

    Source: Kbruder Tech

Conclusion

To review, a string of code which is accessed from a model TextField is called and filtered in a template. The file_path field is also passed to the filter as an argument. If the file_field is not None, then Pygments will determine the lexer to use by the file name extension. If this fails, it will try to guess (it's pretty accurate). Once the lexer is chosen, the string is converted to colorful HTML.


Links

🌐 Welcome! — Pygments
Pygments official site.

🌐 Custom template tags and filters | Django documentation | Django
You can extend the template engine by defining custom tags and filters using Python, and then make them available to your templates using the {% load %} tag.

🌐 https://ethanschoonover.com/


bitcoin:3MnhNRKgrpTFQWstYicjF6GebY7u7dap4u
Bitcoin Accepted Here

3MnhNRKgrpTFQWstYicjF6GebY7u7dap4u

Please donate some Bitcoin to our site. We can use it to keep improving the site and open up to more members. Any amount will help. Thank you.


litecoin:MT61gm6pdp9rJoLyMWW5A1hnUpxAERnxqg
Litecoin Accepted Here

MT61gm6pdp9rJoLyMWW5A1hnUpxAERnxqg

Please donate some Lite to our site. We can use it to keep improving the site and open it up to more members. Any amount will help. Thank you.