From 125422cde5bdb3f9a6e07fbdd39f9a7d60a4cc04 Mon Sep 17 00:00:00 2001
From: Dorian Lesbre <dorian.lesbre@gmail.com>
Date: Wed, 3 Mar 2021 19:25:18 +0100
Subject: [PATCH] =?UTF-8?q?Prettier=20account=20creation=20form=20(more=20?=
 =?UTF-8?q?shamelessly=20stolen=20code=20from=20jeul=C3=A9e-website)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 accounts/forms.py                             | 126 +++++++++++++++++-
 accounts/views.py                             |   2 +-
 home/static/css/style.css                     |  11 +-
 .../registration/create_account.html          |   5 +-
 4 files changed, 137 insertions(+), 7 deletions(-)

diff --git a/accounts/forms.py b/accounts/forms.py
index a49fa5f..ddbef68 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -2,7 +2,131 @@ from django.contrib.auth.forms import UserCreationForm
 
 from accounts.models import EmailUser
 
-class CreateAccountForm(UserCreationForm):
+from django.utils.safestring import mark_safe
+
+
+class FormRenderMixin:
+	""" A mixin that can be included in any form to make it render to html as we want
+	it on this website.
+
+	The following class variables can be adjusted to tweak display:
+
+	* `tooltip_helptexts`: a list of fields whose helptexts, if any, should be rendered
+	  as a question mark's tooltip instead of being inlined.
+	* `field_groups`: if `None`, the fields will be rendered in order, as eg. `as_p`
+	  would.
+	  Else, can be set to any nested list structure, containing each field name exactly
+	  once. The structure `[['a', 'b'], ['c']]` would then group together the fields a
+	  and b, then group together the single field c.
+
+	"""
+
+	tooltip_helptexts = []
+	field_groups = None
+
+	class BadFieldGroups(Exception):
+		pass
+
+	def as_html(self):
+		""" Render the form to html """
+
+		def get_field_treelike():
+			def map_to_field(treelike):
+				if isinstance(treelike, str):
+					if treelike in self.fields:
+						return {
+							"field": self[treelike],
+							"tooltip": treelike in self.tooltip_helptexts,
+						}
+					raise self.BadFieldFroups
+				return list(map(map_to_field, treelike))
+
+			if self.field_groups is not None:
+				return map_to_field(self.field_groups)
+			else:
+				return [
+					list(
+						map(
+							lambda field: {
+								"field": self[field],
+								"tooltip": field in self.tooltip_helptexts,
+							},
+							self.fields,
+						)
+					)
+				]
+
+		def gen_html(treelike):
+			def gen_node(subtree):
+				if isinstance(subtree, list):
+					return '<div class="fieldgroup">\n{}</div>'.format(
+						gen_html(subtree)
+					)
+				else:  # Simple field
+					inline_helptext_html = (
+						(
+							'    <span class="helptext inline_helptext">'
+							"{inline_helptext}</span>\n"
+						).format(inline_helptext=subtree["field"].help_text)
+						if subtree["field"].help_text and not subtree["tooltip"]
+						else ""
+					)
+					tooltip_html = (
+						(
+							'<span class="tooltip" tabindex="0">\n'
+							'<i class="fa fa-question-circle" aria-hidden="true"></i>\n'
+							'<span class="tooltiptext">\n'
+							"  {tooltiphtml}\n"
+							"</span>\n"
+							"</span>"
+						).format(tooltiphtml=subtree["field"].help_text)
+						if subtree["field"].help_text and subtree["tooltip"]
+						else ""
+					)
+
+					field_classes = "formfield"
+					if subtree["field"].errors:
+						field_classes += " error_field"
+
+					labelled_input_classes = "labelled_input"
+					if subtree["field"].field.widget.input_type in [
+						"checkbox",
+						"radio",
+					]:
+						labelled_input_classes += " checkbox_input"
+
+					html = (
+						'<div class="{field_classes}" id="formfield_{label_for}">\n'
+						'  <div class="{labelled_input_classes}">\n'
+						'    <div class="label_line">\n'
+						'      <label for="{label_for}">{label_text}&nbsp;:</label>\n{errors}'
+						"    </div>\n"
+						"    {field_html}\n"
+            '    <div class="help">{tooltip}{inline_helptext_html}</div>\n'
+						"  </div>\n"
+						"</div>"
+					).format(
+						field_classes=field_classes,
+						labelled_input_classes=labelled_input_classes,
+						errors=subtree["field"].errors or "",
+						label_for=subtree["field"].id_for_label,
+						label_text=subtree["field"].label,
+						tooltip=tooltip_html,
+						inline_helptext_html=inline_helptext_html,
+						field_html=subtree["field"],
+					)
+					return html
+
+			return "\n".join(map(gen_node, treelike))
+
+		fields_html = gen_html(get_field_treelike())
+		with_errors = "{form_errors}\n{fields}\n".format(
+			form_errors=self.non_field_errors() if self.non_field_errors() else "",
+			fields=fields_html,
+		)
+		return mark_safe(with_errors)
+
+class CreateAccountForm(FormRenderMixin, UserCreationForm):
 	"""Form used to register a new user"""
 	class Meta:
 		model = EmailUser
diff --git a/accounts/views.py b/accounts/views.py
index 3212e21..1abacb6 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -29,4 +29,4 @@ def create_account(request):
 			return redirect('home')
 	else:
 		form = CreateAccountForm()
-	return render(request, 'registration/create_account.html', {'form': form})
+	return render(request, 'create_account.html', {'form': form})
diff --git a/home/static/css/style.css b/home/static/css/style.css
index 56d9f12..64c0524 100644
--- a/home/static/css/style.css
+++ b/home/static/css/style.css
@@ -183,9 +183,16 @@ ul.errorlist {
 	color: red;
 	font-size: 0.8em;
 	list-style-type: none;
-	padding-left: 5px;
+	padding: 0 5px 0 0;
+	margin: 0;
+}
+div.formfield {
+	padding-top: 5px
+}
+div.label_line {
+	font-weight: bold;
+	padding: 5px;
 }
-
 div.error {
 	color: red;
 	border: 2px solid red;
diff --git a/home/templates/registration/create_account.html b/home/templates/registration/create_account.html
index d2ca278..d5b4c16 100644
--- a/home/templates/registration/create_account.html
+++ b/home/templates/registration/create_account.html
@@ -7,9 +7,8 @@
 	{% endif %}
 	<form method="post" action="{% url 'accounts:create' %}">
 		{% csrf_token %}
-		<table>
-			{{ form }}
-		</table>
+		{{ form.as_html }}
+		<br>
 		<input type="submit" value="Valider">
 	</form>
 {% endblock %}
-- 
GitLab