When a registered user wants to access restricted functionality on a specific page, it would be more user-friendly to enable him/her to sign in on that page instead of being redirected to a login page.
In a previous tutorial we have enabled registered users to comment easily on articles. When a registered user is not logged in however, he/she would have to go to the login page and then return to the specific article. There a many ways to solve this, one being a pop-up login form accessible via the menu. In this tutorial we will embed a simple login form in the specific article page.
The steps are:
serve()
method of the page,To override the serve()
method add the following to the page model (in our case ArticlePage
):
from allauth.account.forms import LoginForm
def serve(self, request, *args, **kwargs):
response = super().serve(request, 'cms/article_page.html')
response.context_data['login_form'] = LoginForm()
return response
We use the standard LoginForm
from the package allauth; use your own if you have a different one. First we call the super()
method to get the default response. Wagtail returns a TemplateResponse object which has a context_data
field to which we can add the form.
The html-code to add to the template just has a login
field (in our case email
), a password
field and a submit
button:
{% if not request.user.is_authenticated %}
<div class="container mt-4 ml-0">
<form method="POST" id="id_login_form" action="{% url 'account_login' %}" style="display: none">
{% csrf_token %}
<div class="row align-items-end">
<div class="col-6 form-group">
{% with field=login_form.login %}{% include "account/form_field.html" %}{% endwith %}
</div>
<div class="col-4 form-group">
{% with field=login_form.password %}{% include "account/form_field.html" %}{% endwith %}
</div>
<div class="col-2 form-group">
<button id="id_submit_login_form" class="btn btn-outline-primary">{% trans "Sign in" %}</button>
</div>
</div>
</form>
</div>
{% endif %}
We initially hide the form by setting style="display: none"
in the form
label. A link to unhide it is added by:
{% if not request.user.is_authenticated %}
<div id="id_comment_invite" class="container-fluid mt-4">
<p>{% trans "Comment on this article ("%}<a href="#" id="id_login_first_link">{% trans "sign in first" %}</a>{% trans " or confirm by name and email below)" %}</p>
</div>
{% endif %}
Adapt the text to your own needs. Wagtail by default has a block extra_js
in our base template; add it if necessary. Since we are going to use ajax to send our form, make sure that your jQuery can handle it. When you have previously used a jQuery link for Bootstrap it might be a slim version, which cannot handle ajax. Copy the link from https://code.jquery.com/ and replace the existing jQuery link in base.html
at the bottom:
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
On the article_page.html
template add:
{% block extra_js %}
<script>
$(document).ready(function(){
$("#id_login_first_link").click(function(event) {
event.preventDefault();
$("#id_login_form").show();
});
ADD CODE FOR LOGIN FORM
});
</script>
{% endblock %}
This simple Javascript code unhides the login form when the user clicks the link. The event.preventDefault()
command prevents scrolling of the page. The code for the login form to be added above is one ajax command:
$("#id_submit_login_form").click(function(event){
event.preventDefault();
$.ajax({
type:"POST",
url: "{% url 'account_login' %}",
data: $('#id_login_form').serialize(),
success: function(response, status){
$("#id_login_form").hide();
$("#id_login_first_link").hide();
location.reload();
},
error: function(xhr, status, error){
$('#id_login_form').submit();
}
});
});
The event.preventDefault()
prevents submitting the form, which would bring the user to another page. The url to which the POST
form should be sent is account_login
, declared in our authentication app. The data to be sent is the serialized form. On success we hide the form and the link and reload the page. Reloading could be prevented, but then we would have to regenerate a csrf token, to prevent an error when a comment is subsequently sent. Additionally we would have to hide the comment fields that we don't need, and change some more things, such as enabling a user can reply on a given comment (which also requires being logged in). So here we choose the lazy option and reload the page. When the login is not successful, e.g. because the user did not fill in the right password, we do submit the login form. This redirects the user to whatever page a failed login generates. For our purposes here we don't want to handle all sign in and sign up eventualities on the article page.
Migrating the database should not be necessary. Try out if it works as expected.
An additional note on Django Comments Xtd. The package also has a Javascript option which submits comments via ajax as well, without reloading the page. It also polls for new comments. This really works beautifully out-of-the-box. A drawback is that it is not trivial to change the styling of the html-elements; this requires diving into the React code. Integrating the login functionality requires some more work. So we leave it for now and maybe revisit some other time.
If you would like to add editable forms to your Wagtail pages, read on.
Comment on this article (sign in first or confirm by name and email below)