Using django's `reverse_lazy` and concatenizing it - python

I want to insert a resolved URL into a string, used as the help_text of a form field:
class ContactForm(forms.Form):
[...]
email_sender = forms.EmailField(
label="Votre email",
widget=forms.EmailInput(attrs={'disabled': 'disabled'}),
help_text="[...], <a href='{}'>[...]</a>.".format(reverse_lazy('account_email'))
)
But inserting the reversed URL into the string is not possible, because the format function (or any concatenation way I tried) is not "lazy", and wants to produce the output immediately.
I get the following error:
django.core.exceptions.ImproperlyConfigured: The included urlconf 'myproject.urls' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
For example, using the following code works perfectly, but is not what I want :)
email_sender = forms.EmailField(
help_text=reverse_lazy('account_email')
)
So how do I concatenate a string of "lazy" values?

Note: This was written in the times of Django 2. While the essence is still valid as of Django 4, the helper function described in this post is now provided out-of-the-box as format_lazy.
You cannot concatenate lazy strings in Django. The implementation is very basic, it's not even actual lazy strings, it's lazy function calls, they may return other types. When you do reverse_lazy, you do just get a lazy function call with no special other behavior
So, just play by the rules. If you need a string to be lazily computed, create a lazy function yourself:
from django.utils.functional import lazy
def email_sender_help_text():
return "[...], <a href='{}'>[...]</a>.".format(reverse('account_email'))
email_sender_help_text_lazy = lazy(email_sender_help_text, str)
You can now use it:
email_sender = forms.EmailField(
help_text=email_sender_help_text_lazy()
)
Or, for a more general version:
from django.utils.functional import lazy
def format(string, *args, **kwargs):
return string.format(*args, **kwargs)
lazy_format = lazy(format, str)
 
help_text=lazy_format("<a href='{}'>", reverse_lazy('account_email'))
You may also want to check out django.utils.functional.allow_lazy.
Later edit:
Since Django 3, Django provides exactly that helper function, as format_lazy. So use that instead of recreating it.

In the latest versions of django, there is a util function called format_lazy: https://docs.djangoproject.com/en/3.0/ref/utils/#django.utils.text.format_lazy

Related

Custom path parameter parsing drf-yasg and Django

I'm trying to force dry-yasg to properly parse parameters from path. Let's say we have
path('users/<int:user_id>/', whatever.as_view(...))
In swagger docs it is not treated as int, but string instead
I have used
swagger_auto_schema(manual_parameters = [
openapi.Parameter(
name,
openapi.IN_PATH,
description=desc,
type=openapi.TYPE_INTEGER,
required=True
)
]
but it's pretty annoying. I could not find a function/method/class responsible for parsing that. Is there a simple method to change behaviour of this parser based on path, so that if int occurs then openapi.TYPE_INTEGER will be returned instead of string?
drf-yasg determines the parameter type automatically in some situations, and falls back on string if detection fails.
queryset = get_queryset_from_view(view_cls)
for variable in sorted(uritemplate.variables(path)):
model, model_field = get_queryset_field(queryset, variable)
attrs = get_basic_type_info(model_field) or {'type': openapi.TYPE_STRING}
As you can see, it tries to get type based on column type of view queryset. If your parameter name doesn't match anything in the queryset though, you get a string. So your first choice should be to try use a name it can autodetect.
If that doesn't work however, you will need to subclass EndpointEnumerator and override get_path_parameters(), probably easiest to call super().get_path_parameters() and go though each parameter and replace type based on variable name.
To use this class you will need your own OpenAPISchemaGenerator.
use a custom OpenAPISchemaGenerator
override its endpoint_enumerator_class with your own EndpointEnumerator

Django reverse routes - two optional parameters

I have a question about Django and it's routing system. I believe that it can be powerfull, but right now I am struggling with one issue I don't experience when working in other frameworks and I can't seem to get a grip on it. Worth to mention that I don't have much experience with Django at this point.
The issue is simple - I have a view which takes two optional parameters, defined like this
def test_view(id=None, grid=None):
Both parameters are optional and frequently are not passed. Id can only be an integer and grid will never be an integer (it is a special string to control datagrid when I don't want to use sessions). I have a route defined like this:
url(a(r'^test_view (\/(?P<id>\d+))? (\/(?P<grid>[^\/]+))? \/?$'), views.test_view, name='test_view'),
This works great and I am not having trouble with using one-way routes. But when I try to use the reverse function or url template tag, following error occurs:
Reverse for 'test_view' with arguments '('20~id~desc~1',)' and keyword arguments '{}' not found.
In this example I tried to find reverse without the id, just with the grid parameter. I have tried various methods of passing parameters to the reverse function:
(grid, )
(None, grid)
('', grid)
{id=None, grid=grid}
All of them result in same error or similliar one.
Is there a way to implement this in django? Maybe just disable the cool URL for the grid parameter. That is how I do it in for example Nette framework for PHP, isntead of having an url like this: 'localhost/test_view/1/20~id~desc~1' I have url like this: 'localhost/test_view/1?grid=20~id~desc~1'. This would be completely sufficient, but I have no idea how to achive this in Django.
As you note in your question, the best way to achieve this is to use standard GET query parameters, rather than doing it in the path itself. In Django you do that exclusively in the view; the URL itself is then just
url(r'^test_view$', views.test_view, name='test_view'),
and you request it via localhost/test_view?id=1&grid=20~id~desc~1. You get the params from request.GET, which is a dictionary-like object; you can use .get so that it does not raise a KeyError when the key is not provided.
def test_view(request):
id = request.GET.get('id')
grid = request.GET.get('grid')

What's a good way to handle url parameters types?

Imagine an app with the following url.py:
urlpatterns = patterns('',
url(r'^$', views.index),
url(r'^double/(?P<number>\d+)/$', views.double),
)
And this views.py:
def double(request, number=42):
return HttpResponse(2*number)
Obviously, I want number to be always taken as an int. When querying /double/5/, I'll always expect to get 10, not 55.
Are there any good way to handle the parameters typing within Django urls?
Here's a decorator-based solution:
def param_type(**type_spec):
def deco(f):
def view(request, **kwargs):
for k, type_ in type_spec.items():
kwargs[k] = type_(kwargs[k])
return f(request, **kwargs)
return view
return deco
#param_type(number=int)
def double(request, number=42):
return HttpResponse(2*number)
This thread says that auto-conversion isn't a good solution:
Note that automatic conversion wouldn't be a good plan, either. For
example, the next version of my blog converts URLs like 2008/2/25/ to
2008/02/25/ because I want a canonical form. So I need to know if
\d{1,2} matches one of two digits, even if the first one is 0.
Auto-conversion to an integer would remove that capability (and it's
not hard to think of other cases like this). So you'd need
configurabiliy.
It also suggests a solution based on a decorator, but I'm not really convinced, it sounds a bit burdensome:
One solution to your problem could be to write a decorator that takes
a list of types (plus something like None for "don't care") and
automatically converts argument N to the type in the N-th element of
the list before calling your function
Anto,
I think your question is more related to the url. (?P<number>\d+) is a regular expression stating \d only allowing digits it's the same as [0-9]. The + symbol tells it need 1 or more digits. So if you would try to insert something like double/notanumber/ it will not work.
Knowing this you can safely assume that:
1: number will always have a value
2: number will always be a integer (even that django gives it back as a unicode)
I made a small change to your function:
def double(request, number): # removed the =42 because number can never be empty
return HttpResponse(2 * int(number)) # Converted your number to an integer

Why doesn't Django template language allow something like a[ 0 ][ 1 ]?

When I was trying to access a tuple inside a list in the Django template format, I found out I couldn't access it with a[ 0 ][ 1 ], instead I had to use a.0.1.
Suppose that a is something like
a = [
( 'a', 'apple' ),
( 'b', 'bee' ),
]
Why doesn't Django template language support a[ 0 ][ 1 ]? In normal Python programming, a.0.1 would give you a syntax error.
The Django docs on the template API explain this nicely:
Dots have a special meaning in template rendering. A dot in a variable name signifies a lookup. Specifically, when the template system encounters a dot in a variable name, it tries the following lookups, in this order:
Dictionary lookup. Example: foo["bar"]
Attribute lookup. Example: foo.bar
List-index lookup. Example: foo[bar]
The template system uses the first lookup type that works. It's short-circuit logic. Here are a few examples:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ person.first_name }}.")
>>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
>>> t.render(Context(d))
"My name is Joe."
>>> class PersonClass: pass
>>> p = PersonClass()
>>> p.first_name = "Ron"
>>> p.last_name = "Nasty"
>>> t.render(Context({"person": p}))
"My name is Ron."
>>> t = Template("The first stooge in the list is {{ stooges.0 }}.")
>>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
>>> t.render(c)
"The first stooge in the list is Larry."
Variable._resolve_lookup in django.templates.base appears to be the function responsible for this, and hasn't changed much since the oldest revision I can find
You can find some information about this in the django book:
The beginning of the chapter should explain why it works this way:
In the previous chapter, you may have noticed something peculiar in how we returned the text in our example views. Namely, the HTML was hard-coded directly in our Python code, like this:
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
Although this technique was convenient for the purpose of explaining how views work, it’s not a good idea to hard-code HTML directly in your views. Here’s why:
Any change to the design of the page requires a change to the Python code. The design of a site tends to change far more frequently than the underlying Python code, so it would be convenient if the design could change without needing to modify the Python code.
Writing Python code and designing HTML are two different disciplines, and most professional Web development environments split these responsibilities between separate people (or even separate departments). Designers and HTML/CSS coders shouldn’t be required to edit Python code to get their job done.
It’s most efficient if programmers can work on Python code and designers can work on templates at the same time, rather than one person waiting for the other to finish editing a single file that contains both Python and HTML.
For these reasons, it’s much cleaner and more maintainable to separate the design of the page from the Python code itself. We can do this with Django’s template system, which we discuss in this chapter.
...
Dot lookups can be summarized like this: when the template system encounters a dot in a variable name, it tries the following lookups, in this order:
Dictionary lookup (e.g., foo["bar"])
Attribute lookup (e.g., foo.bar) 1
Method call (e.g., foo.bar())
List-index lookup (e.g., foo[2])
The system uses the first lookup type that works. It’s short-circuit logic.
The reason I would say that the django template language doesnt do XYZ way of accessing context data is because generally at that point you are doing too much in the template side vs your view that renders it.
The design decision of their template engine seems lighter than maybe some others which give you more pythonic direct access to data. But ideally you would be formatting th context before passing it in.
You also have the ability to create your own template filters for doing more custom processing of data.
Specific to your question, accesing child members using dot notation is the django template way to try multiple approaches to resolving the member. It tries dictionary keys, attributes, etc. in a certain order. You just use dot notation for everything.

How to prevent auto escape in Django templates?

In the docs it says:
The only exceptions are variables that are already marked as “safe” from escaping, either by the code that populated the variable, or because it has had the safe or escape filters applied."
How does the "populated the variable" part work ? I'm actually looking for a way to declare a template tag as safe in the view. I somehow think it's not a good idea to let a designer decide. My co-worker will just add it whenever she 'thinks' it's a good idea.
https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs
Django has a subclass of strings called safe strings (specifically SafeUnicode or SafeString), which can be created using django.utils.safestring.mark_safe. When the template engine comes across a safe string it doesn't perform HTML escaping on it:
>>> from django.utils.safestring import mark_safe
>>> from django.template import Template, Context
>>> Template("{{ name }}").render(Context({'name': mark_safe('<b>Brad</b>')}))
u"<b>Brad</b>"
If you're writing your own template tag, you need to implement render() which will return a string that will be treated as safe, meaning you have to handle any escaping necessary yourself. However if you're writing a template filter, you can set the attribute is_safe = True on the filter to avoid auto escaping of the returned value, e.g.
#register.filter
def myfilter(value):
return value
myfilter.is_safe = True
See https://docs.djangoproject.com/en/4.0/howto/custom-template-tags/#filters-and-auto-escaping for more details.
You could call django.utils.safestring.mark_safe and pass you variable
...
return direct_to_template('my-template.html', {'safe_var': mark_safe('<script>alert("");</script>')})
In template it will be printed without escaping (alert will popup). Though auto-escape is really a great feature that will save you from some bad things.

Categories

Resources