dev fan
dev fan

Reputation: 129

Check if there are only specified Django template variables in a string

I'm working on configurable email template and need to validate if only available_variables are used in the content. So if user put {{ apple }}, the form should return ValidationError('This variable is unavailable').

models.py:

email_content = models.TextField()

forms.py:

def clean_email_content(self):
     email_content = self.cleaned_data['email_content']
     available_variables = ['{{ company_name }}', '{{ user_name }}']
     required_variables = ['{{ company_name }}']
     for required_variable in required_variables:
        if not required_variable in email_content:
                raise forms.ValidationError("{} is required".format(required_variable))
     # how to check if there are only `available_variables` in the content?

TL;DR

email_content cannot contain other variables (strings that starts with {{ and ends with }}) than specified in available_variables

Should I use regex or can I validate this using some method from Django Template Engine?

Upvotes: 2

Views: 483

Answers (2)

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

You may want to use the template engine's lexer instead (nb: django 1.11 code, might need to be adapted if you use django 2.x):

from django.template import base
lexer = base.Lexer("{{ var_one }} is one {% if var_two %}{{ var_two|upper }} is two {% endif %}")
tokens = lexer.tokenize()
for t in tokens:
    if t.token_type == base.TOKEN_VAR:
        print("{}: {}".format(t, t.contents)

I leave it up to you to read the template.base code to find out other useful features...

Also in your validation method you definitly want to catch ALL errors at once (instead of raising as soon as you found an error) so the user can fix all errors at once (unless you really want to drive your users mad, that is).

And finally, as Gasanov suggests in his answer, you also want to use sets to find both missing required vars and illegal ones - this is much more efficient than doing sequential lookups.

Upvotes: 3

Gasanov
Gasanov

Reputation: 3399

We can use regex to find all tags from email_content. After that we convert them to set and substract all available_variables from it to find all incorrect ones. If any exists - we throw ValidationError.

Note that available_variables is set itself and contains just tags, without curly brackets.
Our regex checks for any numbers of spaces between brackets and tag, so your users shouldn't be able to cheat.

import re

def clean_email_content(self):
        email_content = self.cleaned_data['email_content']
        available_variables = {'company_name', 'user_name'}
        finds = re.findall(r"{{[ ]*(.*?)[ ]*}}", email_content)
        invalid_tags = set(finds) - available_variables
        if invalid_tags:
            raise forms.ValidationError(
                "Should not contain invalid tags: " + str(invalid_tags)
            )
        return email_content

Upvotes: 1

Related Questions