How do I differentiate between Django messages in templates while using nested modals? - django

So, I've been racking my brain on this for 2 days now. Any help would be awesome!
At the heart of my issue, I believe, is that I have nested modals and a custom bootstrap form in both: the first for login, and the second for signup. Let's assume in one case I want to do all my validations server-side, and possibly get full control over each of the error validation messages, as well as how and where they should appear respective to their input. How do I do that using django.contrib.messages?
** If I could use some of Bootstrap 4's built-in methods for validation as a first line of defense, or data-validate-on-blur to work like how it does with Zurb Foundation's Abide, even better.
Template tags in each base.html modal:
{% if messages %}
<div class='container-fluid bg-white mt-5 pt-5 pl-4 mb-4'>
{% for message in messages %}
<p class="small font-poller text-danger">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
Trials and tribulations thus far:
As it stands, and with the various work-arounds I've found on Stack Overflow, i.e. using jQuery to toggle the modal (not the prettiest as it reloads the page), the best I've been able to do still bleeds my messages in between modals and/or my redirect views.
I've read threads on how to clear Django messages, and thought that might be a fix, so if after I close a modal or open a new modal, the messages essentially are cleared out until the form is submitted once again. In other words, the login error messages are unique to the login modal when its form's submit button is pressed, and signup error messages are unique to the signup modal when its form's submit button is pressed.
Unfortunately, I haven't figured out how to use a view (views.py), to successfully achieve this. The thought comes to mind that since because I'm using modals to trigger that event, I would have to use jQuery for that, but I have failed on that front also. I'm really hoping there is a more straight-forward solution to this.
Thanks in advance,
Dev
PS - my snippets:
views.py
def signup(request):
signup_errors = User.objects.validation(request.POST, 'register')
if len(signup_errors):
for error in signup_errors.values():
messages.error(request, error)
return redirect('/')
else:
new_user = User.objects.create(
first_name = request.POST['first_name'],
last_name = request.POST['last_name'],
dob = request.POST['dob'],
phone = request.POST['phone'],
address = request.POST['address'],
city = request.POST['city'],
state = request.POST['state'],
zipcode = request.POST['zipcode'],
email = request.POST['email'],
password =
bcrypt.hashpw(request.POST['password'].encode(), bcrypt.gensalt()))
request.session['first_name'] = new_user.first_name
request.session['id'] = new_user.id
messages.info(request, 'You have successfully submitted your
information.')
return redirect('/menu')
def login(request):
login_errors = User.objects.validation(request.POST, 'login')
if len(login_errors):
for error in login_errors.values():
messages.error(request, error)
return redirect('/')
else:
current_user = User.objects.get(email=request.POST['email'])
request.session['first_name'] = current_user.first_name
request.session['id'] = current_user.id
messages.info(request, 'You have successfully logged in.')
return redirect('/menu')
models.py
class UserManager(models.Manager):
def validation(self, postData, error_validation):
errors = {}
if error_validation == 'register':
if not NAME_REGEX.match(postData['first_name']):
errors['first_name'] = "First name can only contain
letters!"
if len(postData['last_name']) < 1:
errors['last_name'] = "Last name cannot be blank."
if not NAME_REGEX.match(postData['last_name']):
errors['last_name'] = "Last name can only contain letters!"
if error_validation == 'login':
user = User.objects.filter(email=postData['email'])
if not user:
errors['user_login'] = "No account with that email in
our system."
elif not bcrypt.checkpw(postData['password'].encode(),
user[0].password.encode()):
errors['password_login'] = "Invalid email and/or
password!"
return errors
login modal in base.html
<div class="modal fade text-dark" id="loginModal">
<div class="modal-dialog">
<div class="modal-content font-paytone">
<div class="modal-header shadow p-3 bg_primary rounded">
<h5 class="modal-title font-poller text-light text_shadow_success2" id="loginModal">Login <i class="fa fa-user text-center ml-1"></i></h5>
<button class="close" data-dismiss="modal"><span>×</span></button>
</div>
<div class="modal-body">
<form id="login-form" action="{% url 'ecommerce_app:login' %}" method="POST" novalidate>
{% csrf_token %}
<div class="form-group">
<input type="email" name="email" class="form-control form-control-lg" placeholder="Email" required>
</div>
<div class="form-group">
<input type="password" name="password" class="form-control form-control-lg" placeholder="Password" required>
</div>
<input id="login-form-submit-btn" type="submit" class="btn btn-success btn-block border bg_primary btn_login" value="Log In">
</form>
<p class="pt-2 font-passion">Don't have an account? Sign up below!</p>
<button id="login-form-signup-btn" class="btn btn-info btn-block border" data-toggle="modal" data-target="#registerModal">Sign Up</button>
</div>
{% if messages %}
<div class="modal-footer">
<div class='container-fluid bg-white'>
{% for message in messages %}
<p class="small font-poller text-danger">{{ message }}</p>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
register modal in base.html
<div class="modal fade text-dark" id="registerModal">
<div class="modal-dialog">
<div class="modal-content font-paytone">
<div class="modal-header shadow p-3 bg_primary rounded">
<h5 class="modal-title font-poller text-light text_shadow_info" id="registerModal">Sign Me Up! <i class="fa fa-user-plus ml-1"></i></h5>
<button class="close" data-dismiss="modal"><span>×</span></button>
</div>
<div class="modal-body">
<form id='signup-form' action="/signup/" method="POST" novalidate>
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
<label for="first_name">First Name</label>
<input type="text" name="first_name" class="form-control" required>
</div>
<div class="form-group col-md-6">
<label for="last_name">Last Name</label>
<input type="text" name="last_name" class="form-control" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="dob">Date of Birth</label>
<input type="date" name="dob" class="form-control" required>
</div>
<div class="form-group col-md-6">
<label for="phone">Phone #</label>
<input type="tel" name="phone" class="form-control" required>
</div>
</div>
<div class="form-group">
<label for="address">Address</label>
<input type="text" name="address" class="form-control" placeholder="Street" required>
</div>
<div class="form-group">
<div class="form-row">
<div class="col-7">
<input type="text" class="form-control" name="city" placeholder="City" required>
</div>
<div class="col">
<input type="text" class="form-control" name="state" placeholder="State" required>
</div>
<div class="col">
<input type="text" pattern="[0-9]{5}" name="zipcode" class="form-control" placeholder="Zip" required>
</div>
</div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<!-- <label for="password">Password</label> -->
<input type="password" name="password" class="form-control" placeholder="Password" required>
</div>
<div class="form-group col-md-6">
<!-- <label for="confirm">Confirm Password</label> -->
<input type="password" name="confirm" class="form-control" placeholder="Confirm Password" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-info btn-block font-fredoka">Register</button>
{% if messages %}
<div class='container-fluid bg-white mt-5 pt-5 pl-4 mb-4'>
{% for message in messages %}
<p class="small font-poller text-danger">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</form>
</div>
</div>
</div>
PSS - if I can help to clarify anything else please let me know

Well, I think I solved my own problem and am somehow compelled to share with others the solution as I think I actually lost sleep over this one. Anyway, I'm not really sure if this is what Django message class 'extra_tags' attribute was originally intended for, but for all intents and purposes it is a clean fix and allows me control over both server-side and client validation messages.
First, I assign extra_tags to 'register' and 'login' respectively when I create and append each message instance:
views.py
def signup(request):
errors = User.objects.validation(request.POST, 'register')
if len(errors):
for error in errors.values():
messages.add_message(request, messages.ERROR, error, extra_tags="register")
return redirect('/')
def login(request):
errors = User.objects.validation(request.POST, 'login')
if len(errors):
for error in errors.values():
messages.add_message(request, messages.ERROR, error, extra_tags="login")
return redirect('/')
I check to see if there are messages, then iterate through them, checking if the tag is 'register' (or 'login'), and if so, render some text, if not, don't render anything.
base.html
{% if messages %}
{% for message in messages %}
{% if 'register' in message.tags %}
<p class="small font-poller text-danger registration_error_message">{{ message }}</p>
{% endif %}
{% endfor %}
{% endif %}
Last but not least, after you submit the form through either modal, you will need to reload the modal with the aforementioned error (or success) messages. To have each modal show its respective messages you will have to differentiate and then toggle to open each one the same way I did using template_tags in the base.html, only this time using a little jQuery:
<script>
$(document).ready(function() {
{% if messages %}
{% for message in messages %}
{% if 'login' in message.tags %}
$('#loginModal').modal('toggle');
{% elif 'register' in message.tags %}
$('#registerModal').modal('toggle');
{% endif %}
{% endfor %}
{% endif %}
});
</script>

Related

How to return form field errors to template rendered in a different django view

I have a django bootstrap 4 template (wordcloud.html) that contains a form. The form was originally created as raw html within wordcloud.html, and the view for that template populates the fields. The form (create_wordcloud_form) uses post to send the data to a different view (create_wordcloud_view.py). The original developer didn't use django's built in template/form/view structure, so there is no form.py nor a form template.
The issue I need to address is validating the form. It has to be server-side validation, as one of the fields needs to be tested against one of the databases to prevent duplicate fields.
Do I need to refactor the whole mess into the standard django template/form/view idiom using crispy-forms, or is there a way to use what I already have and return some error messages from the view that processes the form data?
The code for validating the fields is easy. I am not sure how to structure the return statement in the event there are field errors, since wordcloud.html is rendered in a different view.
create_wordcloud_view:
#method_decorator(login_required, name='post')
class CreateWordCloud(View):
def post(self, request):
logger.debug("CreateWordCloud START")
q_dict = request.POST
logger.debug("q_dict=%s" % q_dict)
sources = []
filters = {}
cloud_name = None
for key in q_dict:
logger.debug("key=%s" % key)
if key == 'csrfmiddlewaretoken':
continue
if key in ["Titles", "Descriptions", "Comments", "Translations", "Text"]:
sources.append(key)
elif key == 'wordcloud_name':
cloud_name = q_dict['wordcloud_name']
else:
values = q_dict.getlist(key)
if len(values) == 1:
filters[key] = values[0]
else:
filters[key] = values
# Need to add field validation here
# 1. Must have at least one source
# 2. wordcloud_name must not be in the table of wordclouds
# 3. Alert user that no words were found for the combination of sources
# and filters selected
from wordcloud.tasks import create_wordcloud_task_3
create_wordcloud_task_3(None, cloud_name, sources, filters=filters)
# If the form data is valid and more than a few words are returned from
# create_wordcloud_task_3, then redirect to wordcloud.html
return redirect("/memorabilia/wordcloud.html", permanent=False)
# If form is invalid or no words returned from create_wordcloud_task_3,
# return to wordcloud.html with original data and appropriate field error
# messages.
return ????
create_wordcloud_form (a snippet from the wordcloud.html template):
<div id="wordcloud_form" style="display: none;">
<form action="/wordcloud/ajax/create_wordcloud/" class="was-validated" method="post">{% csrf_token %}
<div class="form-row">
<div class="col-md-12">
<p>First, select the source of words:</p>
</div>
</div>
<div class="form-row">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="source_text" name="Text">
<label class="form-check-label" for="source_text">Text</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="source_translation" name="Translations">
<label class="form-check-label" for="source_translation">Translations</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="source_titles" name="Titles">
<label class="form-check-label" for="source_titles">Titles</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="source_descriptions" name="Descriptions">
<label class="form-check-label" for="source_descriptions">Descriptions</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="source_comments" name="Comments">
<label class="form-check-label" for="source_comments">Comments</label>
</div>
<span>{{ form_sources_required }}
</div>
<div class="form-row mt-3">
<div class="col-md-12">
<p>Second, filter documents based on the metadata, if desired:</p>
</div>
</div>
{% for category in categories %}
{% if forloop.counter0|divisibleby:3 %}
<div class="form-row">
{% endif %}
<div class="col">
<label>{{ category.label }}</label>
<select class="selectpicker" name="{{ category.label }}" multiple data-size="10">
{% for option in category.options %}
<option>{{ option }}</option>
{% endfor %}
</select>
</div>
{% if forloop.counter|divisibleby:3 %}
</div>
{% endif%}
{% endfor %}
<div class="form-row mt-3">
<div class="col-md-12">
<div class="form-group">
<label for="wordcloud_name">Finally, give your wordcloud a name</label>
<input type="text" name="wordcloud_name" class="form-control" id="wordcloud_name" placeholder="Enter your wordcloud name">
<div class="invalid-feedback">{{ form_cloud_name_required }} {{ form_duplicate_cloud_name_error }}</div>
</div>
</div>
</div>
<div class="form-row mt-3">
<div class="col-md-12">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
A picture of the rendered form:
You can create a django form with the same field names as your html form.
After you submit the form you can check if it valid:
form = YourForm(request.POST)
# check whether it's valid:
if form.is_valid():
...
Then if the form is not valid you can show the errors in your template like so:
<div class="form-group">
<label for="wordcloud_name">Finally, give your wordcloud a name</label>
<input type="text" name="wordcloud_name" class="form-control" id="wordcloud_name" placeholder="Enter your wordcloud name">
<div class="invalid-feedback">{{ form.wordcloud_name.errors }}</div>
</div>

Flask url_for could not build endpoint based on working code. Asks if I want index?

My Flask app already serves up several pages. I just made channels.html. For a route, I copied down and changed /messages, which already works. I then tried adding a link from messages.html to channels.html, but received the error: Could not build url for endpoint 'channels'. Did you mean 'index' instead? The error references the line Channels in messages.html. I checked spelling, syntax, decorator placement, and duplicative names; everything looks ok. Plus, the new code was built off of code that was previously tested.
So, why the error?
#app.route('/channels', methods=["GET", "POST"])
def channels():
return render_template("channels.html")
#app.route('/messages', methods=["GET", "POST"])
def messages():
return render_template("messages.html")
messages.html (top part)
{% extends "layout.html" %}
{% block body %}
<div class="container">
<div class="row" style="min-height: 100vh">
<div class="col-md-3 border border-danger rounded">
<div class="row mt-2 justify-content-start">
Header
</div>
<!-- <div class="row mt-2 justify-content-start" id="channelHeader">
Channels
</div>-->
<div class="row mt-2 justify-content-start" id="channels">
Channels
</div>
<div class="row justify-content-start" id="dmsg">
Direct Messages
</div>
</div>
<div class="col-md-9 border border-danger rounded">
{% endblock %}
channels.html
{% extends "layout.html" %}
{% block body %}
<div class="container">
<div class="display-3">
<strong>Create A Channel</strong>
</div>
<form action="{{ url_for('channels') }}" id="channelForm" class="mt-4">
<!-- Will need check for duplicate email -->
<div class="form-group">
<label for="channelName">Channel Name</label>
<input type="text" class="form-control" id="channelName" aria-describedby="channelName" placeholder="Enter Channel Name">
</div>
<!-- Will need check for duplicate username -->
<div class="form-group">
<label for="inviteUsers">Invite Other Users (optional)</label>
<input type="text" class="form-control" id="inviteUsers" aria-describedby="inviteUsers" placeholder="Search by name">
</div>
<div class="form-group">
<label for="purpose">Purpose</label>
<textarea type="text" class="form-control" id="purpose" rows="3" placeholder="Purpose">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

How to show error messages in a custom signup template in Django

I'm trying to show error message in my custom signup template. Just to verify my below codes work, I used the {{ form.as_table }} and it shows error message properly. However, when I use my custom signup template, it doesn't show any error message when I cause an error on purpose on the signup form.
HTML
<form class="form-signin" method="POST">
{% csrf_token %}
<div class="container">
<div class="jumbotron">
<a class="navbar-brand js-scroll-trigger" href="{% url 'boutique:index' %}"><img width="165" height="30" src="{% static 'boutique/img/modvisor_logo_black.png' %}"></a>
<br><br>
<div class="text-center mb-4">
<h1 class="h3 mb-3 font-weight-normal">Join Modvisor</h1>
<p class="error-message">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
{% endif %}
</p>
</div>
<div class="form-label-group">
<input type="text" id="inputEmail" class="form-control" name="username" placeholder="Email address" required autofocus>
<label for="inputEmail">Email</label>
</div>
<div class="form-label-group">
<input type="password" id="inputPassword1" class="form-control" name="password1" placeholder="Password" required>
<label for="inputPassword">Password</label>
</div>
<div class="form-label-group">
<input type="password" id="inputPassword2" class="form-control" name="password2" placeholder="Password" required>
<label for="inputPassword">Confirm Password</label>
</div>
<!--
<div class="form-label-group">
<input type="password" id="inputPassword" class="form-control" name="password2" placeholder="Password" required>
<label for="inputPassword">Confirm Password</label>
</div>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
-->
<button class="btn btn-success btn-block" type="submit">Sign Up</button>
<br>
<button class="btn btn-secondary btn-block" type="button">Back</button>
<p class="mt-5 mb-3 text-muted text-center">© 2018 Modvisor All Rights Reserved.</p></p>
</div>
</div>
</form>
forms.py
class SignupForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
fields = UserCreationForm.Meta.fields
views.py
def signup(request):
if request.method == 'POST':
form = SignupForm(request.POST)
if form.is_valid():
form.save()
return redirect(settings.LOGIN_URL)
else:
form = SignupForm()
return render(request, 'accounts/register.html', {'form': form})
You should use form.errors.field_name for errors related to specific field. Like for error on username field, it will be form.errors.username
You HTML will look like
<div class='error'>{{form.errors.username}}</div>
<div class="form-label-group">
<input type="text" id="inputEmail" class="form-control" name="username" placeholder="Email address" required autofocus>
<label for="inputEmail">Email</label>
</div>

Django: Redirect Change Password URL

I have the following two URLs:
url(r'^change-password/$',django.contrib.auth.views.password_change,{'template_name': 'meta/changepassword.html', 'post_change_redirect': '/password-changed/'},name='change_password'),
url(r'^change-passwordiOS/$',django.contrib.auth.views.password_change,{'template_name': 'meta/changepassword.html', 'post_change_redirect': '/password-changed/'},name='change_passwordiOS'),
I thought if I used the following within the change password form it would override what URL would be loaded:
{% if 'iOS' in request.path %}
<input type="hidden" name="next" value="/profileiOS/" />
{% endif %}
But when I reach the change password from the url(r'^change-passwordiOS/$' and click the "Change Button" it does not goto the /profileiOS/ as expected but the standard /profile/ URL.
Any help would be appreciated.
/change-password/ view:
#login_required
def password_changed(request):
messages.success(request, 'Your password has been changed.')
return redirect(reverse('profile'))
Full Change Password Form:
<form class="form-horizontal" role="form" method="post" action="">
{% csrf_token %}
<fieldset>
<div class="form-group">
<label class="col-md-6 control-label">{{ form.old_password.label }}:</label>
<div class="col-md-6">
<input name="old_password" type="password" class="form-control"/>
<div class="text-danger">
{% for error in form.old_password.errors %}{{ error }}<br/>{% endfor %}
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-6 control-label">{{ form.new_password1.label }}:</label>
<div class="col-md-6">
<input name="new_password1" type="password" class="form-control"/>
<div class="text-danger">
{% for error in form.new_password1.errors %}{{ error }}<br/>{% endfor %}
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-6 control-label">{{ form.new_password2.label }}:</label>
<div class="col-md-6">
<input name="new_password2" type="password" class="form-control"/>
<div class="text-danger">
{% for error in form.new_password2.errors %}{{ error }}<br/>{% endfor %}
</div>
</div>
</div>
<div class="form-group">
<div class="text-right col-sm-12">
<button type="submit" class="btn btn-primary">Change Password^</button>
</div>
</div>
</fieldset>
{% if 'iOS' in request.path %}
<input type="hidden" name="next" value="/profileiOS/" />
{% endif %}
</form>
You are manually redirecting to /profile/ in password_changed view. You can change your redirect logic to depend on post param from template:
#login_required
def password_changed(request):
messages.success(request, 'Your password has been changed.')
return redirect(request.POST.get('next', reverse('profile')))

Simple GET request returns empty

Here's my HTML code:
{% extends "base.html" %}
{% block content %}
<form class="form-horizontal" action="/application/" method="get">
<div class="control-group">
<label class="control-label" for="userid">User ID</label>
<div class="controls">
<input type="text" id="userid" placeholder="Email">
</div>
</div>
<div class="control-group">
<label class="control-label" for="pass">Password</label>
<div class="controls">
<input type="password" id="pass" placeholder="Password">
</div>
</div>
<div class="control-group">
<div class="controls">
<input type="submit" class="btn" value="Sign in">
<br>
Forgot password?
<br>
Change password?
</div>
</div>
</form>
{% endblock %}
Here's my function in my view:
def application(request):
print request.GET
message=[]
for key,value in request.GET:
message.append("%s:%s"%key,value)
return HttpResponse(message)
Here no matter what I input, my 'print request.GET' prints a <QueryDict: {}>.
I can't figure out how to debug this. Any clue?
none of your inputs have name attributes,
name attributes are what are used to populate data for GET request.
<input type="password" id="pass" name="pass" placeholder="Password">
The above will show up with key name in request.GET
You're returning the last message, I suppose you want to render the template instead?
Try render_to_response instead.