From 5bd093a0a38ebbfe117e0981612be9ffe9e15a03 Mon Sep 17 00:00:00 2001
From: Dan Braghis <dan.braghis@torchbox.com>
Date: Wed, 28 Feb 2018 11:16:03 +0000
Subject: [PATCH] Drop first/last names in favour of a single full_name field

---
 opentech/apply/users/forms.py                 | 26 ++++++++++
 .../migrations/0004_drop_first_last_names.py  | 37 ++++++++++++++
 opentech/apply/users/models.py                | 50 ++++++++++---------
 .../templates/wagtailusers/users/create.html  | 26 ++++++++++
 .../templates/wagtailusers/users/edit.html    | 26 ++++++++++
 opentech/settings/base.py                     |  4 ++
 6 files changed, 146 insertions(+), 23 deletions(-)
 create mode 100644 opentech/apply/users/forms.py
 create mode 100644 opentech/apply/users/migrations/0004_drop_first_last_names.py
 create mode 100644 opentech/apply/users/templates/wagtailusers/users/create.html
 create mode 100644 opentech/apply/users/templates/wagtailusers/users/edit.html

diff --git a/opentech/apply/users/forms.py b/opentech/apply/users/forms.py
new file mode 100644
index 000000000..2a2b97ffc
--- /dev/null
+++ b/opentech/apply/users/forms.py
@@ -0,0 +1,26 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from wagtail.wagtailusers.forms import UserEditForm, UserCreationForm
+
+
+class CustomUserEditForm(UserEditForm):
+    full_name = forms.CharField(label=_("Full name"), required=True)
+
+    def __init__(self, *args, **kwargs):
+        super(CustomUserEditForm, self).__init__(*args, **kwargs)
+
+        # HACK: Wagtail admin doesn't work with custom User models that do not have first/last name.
+        self.fields['first_name'].widget = forms.HiddenInput(attrs={'value': f"fn{self.instance.pk}"})
+        self.fields['last_name'].widget = forms.HiddenInput(attrs={'value': f"ln{self.instance.pk}"})
+
+
+class CustomUserCreationForm(UserCreationForm):
+    full_name = forms.CharField(label=_("Full name"), required=True)
+
+    def __init__(self, *args, **kwargs):
+        super(CustomUserCreationForm, self).__init__(*args, **kwargs)
+
+        # HACK: Wagtail admin doesn't work with custom User models that do not have first/last name.
+        self.fields['first_name'].widget = forms.HiddenInput(attrs={'value': f"fn{self.instance.pk}"})
+        self.fields['last_name'].widget = forms.HiddenInput(attrs={'value': f"ln{self.instance.pk}"})
diff --git a/opentech/apply/users/migrations/0004_drop_first_last_names.py b/opentech/apply/users/migrations/0004_drop_first_last_names.py
new file mode 100644
index 000000000..ec7153baa
--- /dev/null
+++ b/opentech/apply/users/migrations/0004_drop_first_last_names.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-02-28 11:04
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0003_make_email_username'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='user',
+            options={'ordering': ['id']},
+        ),
+        migrations.RemoveField(
+            model_name='user',
+            name='first_name',
+        ),
+        migrations.RemoveField(
+            model_name='user',
+            name='last_name',
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='full_name',
+            field=models.CharField(blank=True, max_length=255, verbose_name='Full name'),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='email',
+            field=models.EmailField(max_length=255, unique=True, verbose_name='email address'),
+        ),
+    ]
diff --git a/opentech/apply/users/models.py b/opentech/apply/users/models.py
index 26a0c2c35..e69cf921f 100644
--- a/opentech/apply/users/models.py
+++ b/opentech/apply/users/models.py
@@ -1,23 +1,10 @@
 from django.db import models
-from django.contrib.auth.models import AbstractUser, BaseUserManager
+from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
+from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
 from .utils import send_activation_email
 
-
-def convert_full_name_to_parts(defaults):
-    full_name = defaults.pop('full_name', ' ')
-    if not full_name:
-        # full_name was None
-        full_name = ' '
-    first_name, *last_name = full_name.split(' ')
-    if first_name:
-        defaults.update(first_name=first_name)
-    if last_name:
-        defaults.update(last_name=' '.join(last_name))
-    return defaults
-
-
 class UserManager(BaseUserManager):
     use_in_migrations = True
 
@@ -28,7 +15,6 @@ class UserManager(BaseUserManager):
         if not email:
             raise ValueError('The given email must be set')
         email = self.normalize_email(email)
-        extra_fields = convert_full_name_to_parts(extra_fields)
         user = self.model(email=email, **extra_fields)
         user.set_password(password)
         user.save(using=self._db)
@@ -50,11 +36,6 @@ class UserManager(BaseUserManager):
 
         return self._create_user(email, password, **extra_fields)
 
-    def get_or_create(self, defaults, **kwargs):
-        # Allow passing of 'full_name' but replace it with actual database fields
-        defaults = convert_full_name_to_parts(defaults)
-        return super().get_or_create(defaults=defaults, **kwargs)
-
     def get_or_create_and_notify(self, defaults=dict(), site=None, **kwargs):
         defaults.update(is_active=False)
         user, created = self.get_or_create(defaults=defaults, **kwargs)
@@ -63,9 +44,23 @@ class UserManager(BaseUserManager):
         return user, created
 
 
-class User(AbstractUser):
+class User(AbstractBaseUser, PermissionsMixin):
+    email = models.EmailField(_('email address'), max_length=255, unique=True)
+    full_name = models.CharField(verbose_name='Full name', max_length=255, blank=True)
+    is_staff = models.BooleanField(
+        verbose_name='staff status',
+        default=False,
+        help_text='Designates whether the user can log into this admin site.',
+    )
+    is_active = models.BooleanField(
+        verbose_name='active',
+        default=True,
+        help_text='Designates whether this user should be treated as active. '
+                  'Unselect this instead of deleting accounts.',
+    )
+    date_joined = models.DateTimeField(verbose_name='date joined', default=timezone.now)
+
     USERNAME_FIELD = 'email'
-    email = models.EmailField(_('email address'), unique=True)
     REQUIRED_FIELDS = []
 
     # Remove the username field which is no longer used
@@ -73,5 +68,14 @@ class User(AbstractUser):
 
     objects = UserManager()
 
+    class Meta:
+        ordering = ['id']
+
     def __str__(self):
         return self.get_full_name()
+
+    def get_full_name(self):
+        return self.full_name.strip()
+
+    def get_short_name(self):
+        return self.email
diff --git a/opentech/apply/users/templates/wagtailusers/users/create.html b/opentech/apply/users/templates/wagtailusers/users/create.html
new file mode 100644
index 000000000..8d546b4a2
--- /dev/null
+++ b/opentech/apply/users/templates/wagtailusers/users/create.html
@@ -0,0 +1,26 @@
+{% extends "wagtailusers/users/create.html" %}
+
+{% block fields %}
+    {% if form.separate_username_field %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.username_field %}
+    {% endif %}
+    {% include "wagtailadmin/shared/field_as_li.html" with field=form.email %}
+    {% include "wagtailadmin/shared/field_as_li.html" with field=form.full_name %}
+
+    {% comment %}
+        First/last name hidden input with dummy values because.. Wagtail admin
+        See opentech.apply.users.forms.CustomUserCreationForm
+    {% endcomment %}
+    {{ form.first_name }}
+    {{ form.last_name }}
+
+    {% if form.password1 %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.password1 %}
+    {% endif %}
+    {% if form.password2 %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.password2 %}
+    {% endif %}
+    {% if form.is_active %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_active %}
+    {% endif %}
+{% endblock fields %}
diff --git a/opentech/apply/users/templates/wagtailusers/users/edit.html b/opentech/apply/users/templates/wagtailusers/users/edit.html
new file mode 100644
index 000000000..8eb3ddb49
--- /dev/null
+++ b/opentech/apply/users/templates/wagtailusers/users/edit.html
@@ -0,0 +1,26 @@
+{% extends "wagtailusers/users/edit.html" %}
+
+{% block fields %}
+    {% if form.separate_username_field %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.username_field %}
+    {% endif %}
+    {% include "wagtailadmin/shared/field_as_li.html" with field=form.email %}
+    {% include "wagtailadmin/shared/field_as_li.html" with field=form.full_name %}
+
+    {% comment %}
+        First/last name hidden input with dummy values because.. Wagtail admin
+        See opentech.apply.users.forms.CustomUserEditForm
+    {% endcomment %}
+    {{ form.first_name }}
+    {{ form.last_name }}
+
+    {% if form.password1 %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.password1 %}
+    {% endif %}
+    {% if form.password2 %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.password2 %}
+    {% endif %}
+    {% if form.is_active %}
+        {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_active %}
+    {% endif %}
+{% endblock fields %}
diff --git a/opentech/settings/base.py b/opentech/settings/base.py
index afcbf80bc..3e9b38859 100644
--- a/opentech/settings/base.py
+++ b/opentech/settings/base.py
@@ -206,6 +206,10 @@ MEDIA_URL = '/media/'
 
 AUTH_USER_MODEL = 'users.User'
 
+WAGTAIL_USER_EDIT_FORM = 'opentech.apply.users.forms.CustomUserEditForm'
+WAGTAIL_USER_CREATION_FORM = 'opentech.apply.users.forms.CustomUserCreationForm'
+WAGTAIL_USER_CUSTOM_FIELDS = ['full_name']
+
 LOGIN_URL = 'users:login'
 LOGIN_REDIRECT_URL = 'dashboard:dashboard'
 
-- 
GitLab