# Peter Percival Patterson’s Pet Pig Porky

π! I can rattle off 36 significant figures on demand, but how accurate do you really need to be? The JPL uses 15 digits for interplanetary navigation. What is needed, though?

Let’s start with a 1 m diameter circle, and add successive digits to the approximation. How many digits before you’re being absurd? I’ll give a definition of absurd, too: a difference in the circumference of less than the Planck length, approximately $$\LARGE{1.616229(38)\times10^{-35} {\rm m}}$$

• For 1 m, the answer is fairly obvious: 36 significant figures
• For d, 43 significant figures are needed
• For the distance from the sun to Voyager 1, ~141 AU at time of writing, just 49 significant figures
• For the Solar System, out to the distance where we believe the Oort cloud ends, ~100000 AU (i.e., the edge of the solar system), 53 significant figures
• For the size of our observable universe, roughly 93000000000 light years, 63 is enough

What if the Bohr radius (approximate size of a hydrogen atom) was our standard, at $$\LARGE{5.2917721067(12)\times10^{-11}{\rm m}}$$? For the size of the observable universe, we only need 36 figures. Handily, that’s what I know! How much would one need for a circle the size of the orbit of Neptune? 23 digits.

So how accurate is JPL if they were measuring the circumference of a circle at the orbit of Neptune? They could be off by as much as a centimeter! Which, assuming appropriate feedback systems, isn’t going to be anything near significant.

How many do we know? 22459157718361 digits. Why? As George Leigh-Mallory put it, “Because it’s there”.

# Who Are You

There seems to be a dearth of articles on setting up full-fledged “automatic” user registration systems for Django 2.0. Yes, there’s documentation, but there’s a lot to wade through, and it’s far from a step-by-step guide, which is what this aspires to be.

The problem for developing many web applications using Django is that adding a user requires logging in through the admin interface to do so. For smaller groups, say, 20 or so, this might be manageable, though annoying. If you develop for deployment to a wide audience, say the entire English speaking world, you don’t want people to email you requests all the time to be added so they can do whatever in your app.

I’ll be assuming that you have a passing familiarity with Django, and have made it through the tutorial and polls app. I’m also assuming that this is among the first things you’re setting up, and you won’t mind blowing away any data you have, because that’s the simplest way to do things in Step 3.

## Step 1: Settings

You can customize these as your needs dictate, but here’s a bunch more things to add to your mysite.settings:

LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
## Possible email backends. The uncommented one, also the default, is the one we want to use
##   by the end, but the others can be used in development. I find console the simplest for
##   that.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
## Console
#EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
## File
#EMAIL_BACKEND = 'django.core.mail.backends.file.EmailBackend'
#EMAIL_FILE_PATH =  '/tmp/app-messages'
## In Memory (django.core.mail.outbox is a list of EmailMessage instances)
#EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
## Dummy (does nothing--same as /dev/null)
#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

## All options below are the default values. Unless you run your own SMTP server on localhost
##   with no authentication required (not recommended), you'll probably want to tweak these.
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_HOST_USER = '' #If this is the empty string, no authentication will be attempted
## The next two options are mutually exclusive.
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_TIMEOUT = None
EMAIL_SSL_KEYFILE = None #If EMAIL_USE_TLS or EMAIL_USE_SSL are True, specify the path to a
#  PEM formatted private key.
EMAIL_SSL_CERTFILE = None #If EMAIL_USE_TLS or EMAIL_USE_SSL are True, specify the path to a
#  PEM formatted certificate chain
EMAIL_SUBJECT_PREFIX = '[Django] '
EMAIL_USE_LOCALTIME = False #The default here will timestamp your message with UTC.

## Step 2: Some Templates

The next thing we want to do is make a bunch of templates. It’s a tad backward from how Django is normally developed, but trust me. As always, customize as you wish.

In your templates directory, create a “registration” subdirectory. All these templates will live in there.

{% extends "base.html" %}

{% block content %}

{% if form.errors %}
{% endif %}

{% if next %}
{% if user.is_authenticated %}
{% else %}
{% endif %}
{% endif %}

<form method="post">
{% csrf_token %}
<table>
<tr>
</tr>
<tr>
</tr>
</table>

<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
{% extends "base.html" %}

{% block content %}

<form method="post">
{% csrf_token %}
<table>
<tr>
<td>{{ form.email.label_tag }}</td>
<td>{{ form.email }}</td>
</tr>
</table>

<input type="submit" value="reset" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
[Django] Password Reset
{% extends "base.html" %}

{% block content %}
<p>You should receieve an email shortly (if an account exists with the e-mail you've
you entered the email you registered with, and check your spam folder.</p>
{% endblock content %}
{% extends "base.html" %}

{% block content %}

<form method="post">
{% csrf_token %}
<table>
<tr>
</tr>
<tr>
</tr>
</table>

<input type="submit" value="reset" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
{% extends "base.html" %}

{% block content %}

{% endblock %}

Play around with the workflow regarding resetting passwords with your superuser, and make sure all these parts play together. If you want to try the same thing with a “regular” user (there won’t be a visible difference unless you make one in the templates), add one through the admin interface.

## Step 3: Altering the User model

Personally, I prefer websites that ask me to log in using my email address. On the wider Internet, it’s guaranteed to be unique so I don’t have to come up with a username like james53401, and I won’t have to remember if I used james53401 on this website or james2557 instead. Yes, I have a password manager that does most of that heavy lifting, but that’s still something that annoys me.

Anyway, the first thing to do would be change the user model to accept an email address instead of a username for login. Easier said than done, though. We get to create a brand new model! You can create an app specifically for the user (./manage.py startapp testuser, which I will assume below) or add the model to an existing app, that’s up to you. Either way, the app will need to be added to the INSTALLED_APPS list in mysite.settings. Also, you’ll need to add this to mysite.settings:

AUTH_USER_MODEL='testuser.MyUser'

Then,

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
import datetime

class MyUserManager(BaseUserManager):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
)
user.save(using=self._db)
return user
user = self.create_user(
email,
)
user.save(using=self._db)
return user
class MyUser(PermissionsMixin,AbstractBaseUser):
email=models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
objects=MyUserManager()
def __str__(self):
return self.email
def is_staff(self):
def is_superuser(self):

from django import forms
from django.contrib.auth.models import Group

from testuser.models import MyUser

class UserCreationForm(forms.ModelForm):
class Meta:
model = MyUser
fields = ('email',)
def save(self, commit=True):
user = super().save(commit=False)
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
#this prevents anyone from accidentally screwing up the password hash
class Meta:
model = MyUser
#this prevents anyone from accidentally screwing up the password hash
form = UserChangeForm
fieldsets = (
)
(None, {
'classes': ('wide',),
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()

# Now register the new UserAdmin...


Now, we delete everything. If you’re using a MySQL, PostgreSQL, etc. type database, drop all tables. If you’re using sqlite3, delete the database file. Delete all migrations folders from the apps. We “get” to start over.

Now run the following:

./manage.py makemigrations testuser
./manage.py migrate

If you run your server now and go to the login page, there’s still no way to create a user. Let’s fix that.

Add the following to the appropriate views.py:

from django.views.generic.edit import CreateView
from django.urls import reverse_lazy

class UserCreationView(CreateView):
form_class=UserCreationForm
template_name='registration/register.html'
success_url=reverse_lazy('login')

urlpatterns = [
path('accounts/register/',views.UserCreationView.as_view(),name='register'),
]

Nearly there!

## Step 5: Verifying the Email Address

I’ve also found in my long years trawling the Internet that verifying email addresses is a necessity. I’ve wound up on a mailing list for a travel agency in South Africa, a Botox clinic in Chicago, a Mazda dealership in Texas, and a home remodelling firm in Denver, associated with people named Johann, Joni, Jacci, and others. Instagram was nice enough to send me an e-mail a few dozen times when a Jessica attempted to use my email address to sign up for an account (to the point where I signed up for one just to stop the emails). But to avoid being a bad apple, let’s add in a way to verify the person signed up for an account intentionally, and entered the right email address.

The way we’ll do this is to not allow the user to set a password until they have verified the email address. This sounds a lot like resetting a password, right? So we’ll take advantage of that.

First thing, change UserCreationForm to the following:

class UserCreationForm(forms.ModelForm):
class Meta:
model = MyUser
fields = ('email',)
def save(self, commit=True):
user = super().save(commit=False)
return user