Reputation: 432
For the past couple days I have been trying to solve a strange problem with the default help_text
on a form I created to handle user signups. I first noticed the issue when I saw that the html django is inserting as the default help_text
is getting escaped.
Instead of displaying the <ul>
, which I remind you is the default help_text
that django includes for password fields, it is displaying plain text.
So this is where I first noticed that must be doing something wrong. If the default form help_text
is getting escaped and looking awful like that, I'm clearly making a mistake. Next I will explain what I did to try to fix this, and then will give a overview of the model
, form
, view
, and template
so you guys have something to work from.
The first solution I found online was to use the Meta
class, so I did in my forms.py
under the class SignUpForm:
that I am modifying.
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class SignUpForm(UserCreationForm):
company = forms.CharField()
city = forms.CharField()
state = forms.CharField()
zip = forms.IntegerField()
address = forms.CharField()
phone = forms.IntegerField()
class Meta:
model = User
# help_text = mark_safe
fields = ('company', 'city', 'state', 'zip', 'address', 'phone', 'username', 'email', 'password1', 'password2')
labels = {
'state': 'US States',
'password1': 'passcode1',
'password2': 'passcode2',
'username': 'human person',
'email': 'telegraph',
'city': 'locality',
'phone': "tele",
}
help_texts = {
'password1': 'Something that doesnt look awful',
'password2': 'Something else',
'username': 'Please enter an appropriate human name.',
'email': 'Which office?',
'city': 'What county?',
'phone': 'Please Include Country Code',
}
This is where I started to realize the problem was bigger than I thought. Not only is something causing a help_text
to be escaped, some of these fields accept my changes while others don't. The custom fields that I have extended the default UserCreationForm
with (in this example city
and phone
don't display their new label
or help_text
, while the default fields username
and email
both display their inane new label
and help_text
. And to top it all off the password1
and password2
fields remain unchanged.
Screenshot of class Meta result
Alright, so that didn't work. What about hardcoding it right into the form? Well it turns out that mostly works, but it introduces another level of complication for me in this example, as well as feeling like bad practices. I'll explain.
Since my form is extending the default django UserCreationForm
I don't actually setup the fields in my SignUpForm
, they are added automatically and I use their fields in the class Meta:
So in order to hardcode my way around this problem I have to add them.
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
class SignUpForm(UserCreationForm):
username = forms.CharField(help_text=mark_safe("Please enter an appropriate human name."), label='human name')
email = forms.CharField(widget=forms.EmailInput, help_text=mark_safe('Which office?'), label='telegraph')
password1 = forms.CharField(widget=forms.PasswordInput, help_text=mark_safe('Something that doesnt look awful'),
label='Passcode')
password2 = forms.CharField(widget=forms.PasswordInput, help_text=mark_safe('Something else'), label='Passcode 2')
company = forms.CharField(help_text=mark_safe("Please enter a company name"))
city = forms.CharField(help_text=mark_safe('What county?'), label='locality')
state = forms.CharField(help_text=mark_safe('Please enter the state'))
zip = forms.IntegerField(help_text=mark_safe('Please enter a zip code.'))
address = forms.CharField(help_text=mark_safe('Please enter an address.'))
phone = forms.IntegerField(help_text=mark_safe('Please include country code.'), label='tele')
class Meta:
model = User
fields = ('company', 'city', 'state', 'zip', 'address', 'phone', 'username', 'email', 'password1', 'password2')
So this one works, but it is really impractical and concerning as I haven't solved the root issue.
Screenshot of hardcoding result (can't post because I don't have enough rep, but trust me everything works)
So that brings us to now, I've tried a few other things, but nothing has gotten me as close to what I want as hardcoding, so I need to figure out the underlying mistake I have made.
So here is what I am working with:
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
username = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.TextField(max_length=500, blank=True)
city = models.CharField(max_length=100, blank=True)
state = models.CharField(max_length=100, blank=True)
zip = models.CharField(max_length=5, blank=True)
address = models.CharField(max_length=200, blank=True)
phone = models.CharField(max_length=12, blank=True)
@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
forms.py (current hardcoded version):
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
class SignUpForm(UserCreationForm):
username = forms.CharField(help_text=mark_safe("Please enter an appropriate human name."), label='human name')
email = forms.CharField(widget=forms.EmailInput, help_text=mark_safe('Which office?'), label='telegraph')
password1 = forms.CharField(widget=forms.PasswordInput, help_text=mark_safe('Something that doesnt look awful'),
label='Passcode')
password2 = forms.CharField(widget=forms.PasswordInput, help_text=mark_safe('Something else'), label='Passcode 2')
company = forms.CharField(help_text=mark_safe("Please enter a company name"))
city = forms.CharField(help_text=mark_safe('What county?'), label='locality')
state = forms.CharField(help_text=mark_safe('Please enter the state'))
zip = forms.IntegerField(help_text=mark_safe('Please enter a zip code.'))
address = forms.CharField(help_text=mark_safe('Please enter an address.'))
phone = forms.IntegerField(help_text=mark_safe('Please include country code.'), label='tele')
class Meta:
model = User
fields = ('company', 'city', 'state', 'zip', 'address', 'phone', 'username', 'email', 'password1', 'password2')
views.py:
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from apps.dashboard.forms import SignUpForm
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save()
user.refresh_from_db() # load the profile instance created by the signal
user.profile.company = form.cleaned_data.get('company')
user.profile.city = form.cleaned_data.get('city')
user.profile.state = form.cleaned_data.get('state')
user.profile.zip = form.cleaned_data.get('zip')
user.profile.address = form.cleaned_data.get('address')
user.profile.phone = form.cleaned_data.get('phone')
user.save()
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
login(request, user)
return redirect(main)
else:
form = SignUpForm()
return render(request, 'signup.html', {'form': form})
template (html):
<h2>Sign up</h2>
<form method="post">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }}<br>
{{ field }}
{% if field.help_text %}
<small style="color: grey">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
</p>
{% endfor %}
<button type="submit">Sign up</button>
</form>
Upvotes: 2
Views: 2552
Reputation: 496
To answer the OPs original concern about the help_text being escaped:
The 'safe' filter can be used, for example...
{% if field.help_text %}
<small style="color: grey">{{ field.help_text|safe }}</small>
{% endif %}
gives the list you're looking for in the rendered template.
You might want to see the SO post entitled Django template escaping for more examples of this behaviour and how to control it.
Upvotes: 3
Reputation: 308829
It looks like help_texts
will only work for model fields like username
and email
. For other fields, you can set the help_text
in the __init__
method.
class SignUpForm(UserCreationForm):
class Meta:
model = User
...
help_texts = {
'username': 'Please enter an appropriate human name.',
'email': 'Which office?',
}
def __init__(self, *args, **kwargs):
super(SignUpForm, self).__init__(*args, **kwargs)
self.fields['password1'].help_text = 'Something that doesnt look awful'
self.fields['password2'].help_text = 'Something else'
Instead of adding the profile fields to the SignUpForm
, I would create a separate model form for profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
...
Then include both forms in your view and template.
Upvotes: 4