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} :</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