astral-sh / ruff

An extremely fast Python linter and code formatter, written in Rust.
https://docs.astral.sh/ruff
MIT License
32.84k stars 1.1k forks source link

[django] New rule: Check that enums do not have a `do_not_call_in_templates` member #13969

Open Kakadus opened 2 weeks ago

Kakadus commented 2 weeks ago

In django templates, if a template variable is callable, the template system will try calling it. To disable that, one can define a truthy do_not_call_in_templates attribute on the callable [ref. django docs].

A fallacy might be to do something like the following:

from enum import Enum, auto

class AwesomeEnum(Enum):
    do_not_call_in_templates = True

    CASE_A = auto()
    CASE_B = auto()

Note that that leads do_not_call_in_templates to be e.g. in __members__, and maps to AwesomeEnum(True):

>>> AwesomeEnum.__members__
{'do_not_call_in_templates': <AwesomeEnum.do_not_call_in_templates: True>, 'CASE_A': <AwesomeEnum.CASE_A: 2>, 'CASE_B': <AwesomeEnum.CASE_B: 3>}

Instead, it should either be wrapped in an enum.nonmember or defined in a @property, so that it is not exported as a member of the enum like django itself does it.

A correct Enum would be defined like so:

from enum import Enum, auto, nonmember

class AwesomeEnum(Enum):
    do_not_call_in_templates = nonmember(True)

    CASE_A = auto()
    CASE_B = auto()

or (as there is no nonmember in <3.11):

class AwesomeEnum(Enum):
    @property
    def do_not_call_in_templates(self):
        return True

    CASE_A = auto()
    CASE_B = auto()

Maybe this is can be implemented as a nice little ruff rule? We just noticed this after having a do_not_call_in_templates member in our enum for nearly 4 years :smile:

MichaReiser commented 2 weeks ago

Thanks for the nice write up. This does make sense to me. The only decision we have to make here is to which extend we want to have django rules in general.