Hi all,
I've been looking at bug #24977 (Template variables with a value of
None are considered to be == to non-existent properties). The problem
is that in a template:
{% if user.pk == some_object.invalid_property %}
... this gets rendered when the user is logged out
{% endif %}
This is because undefined variables get silently expanded to None,
which can lead to a silent application bug, potentially a security
hole, if an object is refactored so the attribute name in the
comparison is no longer valid.
The problem is that None has a perfectly valid meaning to the
application in some circumstances (such as when it indicates a
logged-out user's PK), so its meaning gets overloaded by using it to
mean an invalid variable (which IMO should never compare equal to
anything else).
The suggestion on the ticket is to use an instance of a special
`Undefined` class to indicate an undefined variable, which could never
compare equal to anything else.
This sounds sensible, but in implementing it I got to thinking about
whether this can generalise and simplify. It seems like it might be
possible to combine both Engine.string_if_invalid and the
ignore_failures parameter to FilterExpression.resolve:
* The Undefined object can have a __str__() that returns the
string_if_invalid value, so it acts like a string for the purposes
of rendering.
* Returning the Undefined value is also sufficient to provide the
behaviour we need in the ignore_failures case: the calling code can
check for Undefined in the same case where it currently checks for
None.
Overall the code is simpler, because a bunch of code paths are
eliminated. However, there are 2 issues:
1) There are test cases where we have templates that should treat "x
is y" as True where x and y are both undefined. This means that
(unless we want to change the behaviour) we have to return multiple
copies of a single Undefined instance, rather than a fresh instance
each time. And this doesn't work in the case where
string_if_invalid contains a %s and the string value should have
the variable name expanded into it.
I don't see any way to satisfy both these requirements. Is it
reasonable to relax the requirement that "x is y" should be treated
as True when both variables are undefined?
2) There appears to be an inconsistency in the default_if_none
modifier. If I have a template with
x|default_if_none:y = {{x|default_if_none:y}}
{% if x|default_if_none:y %}
x|default_if_none:y is apparently True
{% endif %}
with y defined to True and x undefined, then it produces:
x|default_if_none:y =
x|default_if_none:y is apparently True
IOW it appears that the default_if_none doesn't cause y's value to
be used in the case where the variable is being printed, even
though it causes the expression to have y's value when evaluated in
an if. I think this is something to do with the fact that the two
ways of handling failures (ignore_failures and string_if_invalid)
do different things.
I don't see a way to make backward compatible code here.
I think this is just a bug, does anyone think otherwise?
Sorry for the lengthy email. Thanks for any input.
Tim
--
You received this message because you are subscribed to the Google Groups
"Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-developers/b9d7b557-5826-4c7e-a064-5f8b59312faa%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.