FOSUserBundle: How to best integrate login and register form in one template? - templates

I am using the FOSUserBundle in my Symfony application which is really great. They have the login and register forms in a separate template. I want both in one template to display them next to each other.
Therefore I created app/Resources/FOSUserBundle/Security/login.html.twig and app/Resources/FOSUserBundle/Registration/register.html.twig to override both templates. In login.html.twig I call the register controller to render its template.
app/Resources/FOSUserBundle/Security/login.html.twig:
{% extends "FOSUserBundle::layout.html.twig" %}
{% trans_default_domain 'FOSUserBundle' %}
{% block fos_user_content %}
{% if error %}
<div>{{ error|trans }}</div>
{% endif %}
<form action="{{ path("fos_user_security_check") }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />
<input type="text" id="username" name="_username" value="{{ last_username }}" placeholder="{{ 'security.login.username'|trans }}" required="required" />
<input type="password" id="password" name="_password" placeholder="{{ 'security.login.password'|trans }}" required="required" />
<input type="checkbox" id="remember_me" name="_remember_me" value="on" />
<label for="remember_me">{{ 'security.login.remember_me'|trans }}</label>
<input type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans }}" />
</form>
{{ render(controller('FOSUserBundle:Registration:register')) }}
{% endblock fos_user_content %}
app/Resources/FOSUserBundle/Registration/register.html.twig:
{% include "FOSUserBundle:Registration:register_content.html.twig" %}
But then I get the following error:
FatalErrorException: Error: Maximum function nesting level of '100' reached, aborting! in /private/var/www/symfony/My_UserBundle/vendor/twig/twig/lib/Twig/Node/Expression/Array.php line 31
I don't know why I get this error message. If I look into the Security:login and Registration:register controller, the templates get rendered in different ways:
$this->renderLogin(...
and
$this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html....
So basically I have two questions:
What does the error message mean and how can I solve it?
Maybe this approach is not ideal, is there a better solution for this?

You could use (as #Sidali Hallak said)
{% render url('fos_user_registration_register') %}
{% render url('fos_user_security_login') %}
But use your own versions of the FOSUserBundle:Registration:register.html.twig and FOSUserBundle:Security:login.html.twig templates that don't extend FOSUserBundle::layout.html.twig

To be compliant with newer Symfony versions, you should avoid using {% render %} tag and use {{ render(controller('MyBundle:ControllerClass:action')) }} instead:
{{ render(controller('FOSUserBundle:Security:login')) }}
{{ render(controller('FOSUserBundle:Registration:register')) }}

use :
{% render url('fos_user_security_login') %}
{% render url('fos_user_Registration_register') %}

The max nesting level comes from xdebug and can be resolved setting ...
xdebug.max_nesting_level = 200
... in your php.ini.

Related

Django formtools SessionWizardView repopulating form on validation error

I've a django-formtools SessionWizardView wizard which works fine when all data is input. However, I have form validation going on within each step of the form and if a step is represented I cannot get the entered data to redisplay in some instances.
Here is a simple example. Required field description isn't entered byt field plan was entered. The validation error is reported and the form redisplayed.
I'm creating the plan checkboxes in the template as below.
{% for plan in PLANS %}
<div class="col-6">
<span class="form-radio form-radio-xl">
<input type="radio" id="id_job-plan_{{plan.id}}" name="job-plan" value="{{ plan.pk }}" required {% if wizard.form.plan.value == plan.pk %}checked{% endif %}>
<label for="id_job-plan_{{plan.id}}">{{ plan }} - {{plan.pk}} </label>
</span>
</div>
{% endfor %}
I expect {% if wizard.form.plan.value == plan.pk %}checked{% endif %} to be True in one instance and therefore checked. It isn't and I do not understand why not.
If I do {{ wizard.form.plan.value }} the displayed result looks the same as {{ plan.pk }}
Found the answer. It looks the same but {{ wizard.form.plan.value }} is a string. By using {{ wizard.form.plan.value|add:"0" }} I coerce it into an integer and now it works

set_language not changing language in certain cases (django)

I have the following menu to change languages in my site:
<div class="btn-group navbar-right language menu">
<button class="btn btn-secondary btn-sm dropdown-toggle language-btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% get_language_info for LANGUAGE_CODE as lang %}
{{ lang.name_local }} ({{ lang.code }})
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu2">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<ul class="language-item">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}" />
<input name="language" type="hidden" value="{{ language.code }}" />
<input type="submit" value="{{ language.name_local }} ({{ language.code }})" /> <span>{% if language.code == LANGUAGE_CODE %}✓{% endif %}</span>
</form>
</ul>
{% endfor %}
</div>
</div>
Being shown like
This works the same than the form suggested by Django docs but avoiding the "Go" button to switch languages.
It usually switches languages properly, except in certain cases, as I found out after a user reported the problem.
If the user gets to my site via www.mydomain.com, it will be redirected to www.mydomain.com/en/ (or /es/ or /de/) and as far as I know, changing languages works as expected.
However, if the user gets to my site via www.mydomain.com/es/, then when trying to change the language through the menu, www.mydomain.com/es/ is loaded again. The same behavior occurs with www.mydomain.com/de/.
Strange enough, when entering my site via www.mydomain.com/en/, changing languages works properly. Maybe because it is the default language?
I am able to replicate the issue consistently if entering the site through an incognito window. If I do it through a normal window, it is a similar behavior, but not always consistent. For instance, sometimes entering via www.mydomain.com/en/ will not let me change the language either. This makes me think it might have something to do with cookies. But this is as far as I got.
I checked for similar problems during hours and the only similar thing I found is the Django ticket In some cases i18n set_language does not change url language. Its conclusion is:
...I recreated the issue: we set a language (once) then try to reverse
a URL from the old language when setting the language again.
This obviously fails.
Workarounds:
keeping track of the old language and falling back to try that if the lookup with the current language fails.
Signalling across browser tabs that we already changed the language and adjusting accordingly (???).
Both of these are out of scope for the in-built i18n. (The first would
be possible on a project level — reimplementing e.g. set_language — if
it was deemed cost effective.)
I'm going to close this on that basis.
I am not sure if it is the same case, as I don't understand why it "obviously fails". As I see it, the obvious behavior should be to change the language.
I am surprised that such a basic feature does not work, so probably I am missing something.
Why is it not working in the cases described?
Any help would be appreciated.
Edit 1:
Previously I was using translated URLs instead of the form, as suggested here. However, this was taking 1 second to render due to the translation of the slug. This is why I decided to change
I experienced the same problem. It is still an existing issue, where django seemingly does not translate the HTTP referer header.
See Django source code of set_language
A way to workaround this is to send a hidden input field named next that you set to the same url you are at (request.get_full_path), but replace the current language part with the language you intend to switch to.
Here is a snippet of a language switcher solving this issue:
<li {% if current_language|slice:":2" == 'en' %} class="active"{% endif %}>
<form method="POST" action="{% url 'set_language' %}" autocomplete="off">
{% csrf_token %}
<span class="language-code">en</span>
<input type="submit" name="language" value="en">
<input type="hidden" name="next" value="{{ request.get_full_path|replace_language:"en" }}">
</form>
</li>
<li{% if current_language|slice:":2" == 'da' %} class="active"{% endif %}>
<form method="POST" action="{% url 'set_language' %}" autocomplete="off">
{% csrf_token %}
<span class="language-code">dk</span>
<input type="submit" name="language" value="da">
<input type="hidden" name="next" value="{{ request.get_full_path|replace_language:"da" }}">
</form>
</li>
Then you can implement a custom template tag called replace_language that runs through your configured languages and replaces it with the argument.
Hope this helps :-)
For further reference, I just went back to using translated URLs as suggested here, instead of the form.
I found out that what was making it so slow.
It turned out it was something that could be optimized, so now the translated URLs load very fast as well.
Solved the issue by removing the language code from the "next" input (slice the current language code from the url)
Next input:
<input name="next" type="hidden" value="{{ request.get_full_path|slice:"3:" }}">
Whole dropdown:
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<div class="row g-2">
<div class="col col-sm-12 col-lg-3 col-md-4">
<div class="form-floating">
<input name="next" type="hidden" value="{{ request.get_full_path|slice:"3:" }}">
<select class="form-select" name="language" onchange="this.form.submit()" id="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<label for="language">Language</label>
</div>
</div>
</div>
</form>

Visual Studio Code formats on save no matter what I do. How do I stop this?

I am using python (django syntax) inside an html file:
<form action="{% url 'home:favorite' album.id %}" method="POST">
{% csrf_token %}
{% for song in album.song_set.all %}
<input type="radio" id="song{{ forloop.counter }}" name="song" value="{{ song.id }}" />
<label for="song{{ forloop.counter }}">
{{ song.song_title }}
{% if song.is_favorite %}
**
{% endif %}
</label><br>
{% endfor %}
<input type="submit" value="Favorite">
</form>
No matter what I do in the VS Code settings.json (for user OR workspace) it formats the code like this:
<form action="{% url 'home:favorite' album.id %}" method="POST">
{% csrf_token %} {% for song in album.song_set.all %}
<input type="radio" id="song{{ forloop.counter }}" name="song" value="{{ song.id }}" />
<label for="song{{ forloop.counter }}">
{{ song.song_title }}
{% if song.is_favorite %}
**
{% endif %}
</label>
<br> {% endfor %}
<input type="submit" value="Favorite">
</form>
(pushes the {% for song in album.song_set.all %} among other things to the previous line and keeps it from being on its own line.
Here are my vs code settings:
{
"editor.fontSize": 18,
"editor.formatOnType": false,
"editor.formatOnSave": false,
"editor.glyphMargin": true,
"editor.tabSize": 4,
"editor.detectIndentation": false,
"editor.wordWrap": true
}
These settings are the same for both user and workspace. Am I missing something?
Thanks,
Zach
Turns out the JS-CSS-HTML Formatter extension for VS Code was formatting it that way and I had to disable it.
Zach

Django-allauth: how does django know which form to use in form.as_p?

I am just learning Django especially using allauth to build a login/signup module.
I am reading the source code of allauth-master/allauth/templates/account/login.html and I came across the line {{ form.as_p }}
<form class="login" method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
{{ form.as_p }}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
I know the function of "{{ form.as_p }}". But it became confusing as I further looked into allauth-master/allauth/account/forms.py: there are a lot of classes/forms there.
Just want in login.html, how does Django know to use the "LoginForm" rather than other forms?

invalid block-tag when I implement i18n

I'm currently adding i18n in my website but there is something wrong.
When I use code from djangoproject
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}">{{ language.name_local }} ({{ language.code }})</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
I get this error
Invalid block tag: 'get_language_info_list'
I don't understand why get_language_info_list is unknown. Templates_context_processors is ok. HTML form is on my homepage.
Always make sure you load the tag library first before you requests any tags. To use get_language_info_list, you need to make sure {% load i18n %} is in your template.