From b81fb8cf7140b06b3c64075e03f437c14e7c76a3 Mon Sep 17 00:00:00 2001
From: kaiyou <pierre@jaury.eu>
Date: Tue, 12 May 2020 17:28:55 +0200
Subject: [PATCH] Add quick profile creation for the first profile

---
 hiboo/models.py                            |  3 ++
 hiboo/profile/__init__.py                  | 21 ++++++++-----
 hiboo/profile/format.py                    |  6 ++--
 hiboo/profile/forms.py                     | 12 +++++++-
 hiboo/profile/login.py                     | 34 +++++++++++++---------
 hiboo/profile/templates/profile_quick.html | 30 +++++++++++++++++++
 6 files changed, 82 insertions(+), 24 deletions(-)
 create mode 100644 hiboo/profile/templates/profile_quick.html

diff --git a/hiboo/models.py b/hiboo/models.py
index eb40676c..fd1205da 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -162,6 +162,9 @@ class Service(db.Model):
     same_username = db.Column(db.Boolean(), nullable=False, default=False)
     config = db.Column(mutable.MutableDict.as_mutable(JSONEncoded))
 
+    def check_username(self, username):
+        return Profile.query.filter_by(service_uuid=self.uuid, username=username).first()
+
 
 class Profile(db.Model):
     """ A profile is a per-service custom identity.
diff --git a/hiboo/profile/__init__.py b/hiboo/profile/__init__.py
index 61168d90..fa7865f6 100644
--- a/hiboo/profile/__init__.py
+++ b/hiboo/profile/__init__.py
@@ -15,13 +15,18 @@ from hiboo.profile import login, admin, forms, cli
 
 
 def get_profile(service, **redirect_args):
+    query = models.Profile.query.filter_by(
+        service_uuid=service.uuid,
+        user_uuid=flask_login.current_user.uuid,
+        status=models.Profile.ACTIVE
+    )
     form = forms.ProfilePickForm()
     if form.validate_on_submit():
-        profile = models.Profile.query.get(form.profile_uuid.data)
-        if not (profile and
-                profile.user == flask_login.current_user and
-                profile.service == service and
-                profile.status == models.Profile.ACTIVE):
-            return None
-        return profile
-    utils.force_redirect(utils.url_for("profile.pick", **redirect_args))
+        return query.filter_by(uuid=form.profile_uuid.data).first()
+    count = query.count()
+    if count > 1:
+        utils.force_redirect(utils.url_for("profile.pick", **redirect_args))
+    elif count == 1:
+        return query.one()
+    else:
+        utils.force_redirect(utils.url_for("profile.create_quick", **redirect_args))
diff --git a/hiboo/profile/format.py b/hiboo/profile/format.py
index c5390316..9033bcd7 100644
--- a/hiboo/profile/format.py
+++ b/hiboo/profile/format.py
@@ -44,15 +44,17 @@ class ProfileFormat(object):
     def coalesce(cls, username):
         """ Transform a username into its valid form
         """
-        return filter(cls.allowed.__contains__, cls.transform(username))
+        return ''.join(filter(cls.allowed.__contains__, cls.transform(username)))
 
     @classmethod
     def alternatives(cls, username):
         """ Generate alternate usernames for a given username
         """
+        yield username
         index = 1
         while True:
-            yield username + "_" + str(index)
+            yield username + str(index)
+            index += 1
 
 
 register = ProfileFormat.register
diff --git a/hiboo/profile/forms.py b/hiboo/profile/forms.py
index 51c4721e..51679114 100644
--- a/hiboo/profile/forms.py
+++ b/hiboo/profile/forms.py
@@ -1,4 +1,4 @@
-from wtforms import validators, fields
+from wtforms import validators, fields, widgets
 from flask_babel import lazy_gettext as _
 
 import flask_wtf
@@ -9,6 +9,16 @@ class ProfileForm(flask_wtf.FlaskForm):
     comment = fields.StringField(_('Comment'))
     submit = fields.SubmitField(_('Create profile'))
 
+    def force_username(self, username):
+        self.username.data = username
+        self.username.render_kw = {"readonly": True}
+
+    def hide_fields(self):
+        self.username.widget = widgets.HiddenInput()
+        self.username.label = ""
+        self.comment.widget = widgets.HiddenInput()
+        self.comment.label = ""
+
 
 class ProfilePickForm(flask_wtf.FlaskForm):
     profile_uuid = fields.TextField('profile', [])
diff --git a/hiboo/profile/login.py b/hiboo/profile/login.py
index 07741731..1eb65c24 100644
--- a/hiboo/profile/login.py
+++ b/hiboo/profile/login.py
@@ -2,7 +2,6 @@ from hiboo.profile import blueprint, forms, formats
 from hiboo import models, utils, security
 from hiboo import user as hiboo_user
 from passlib import context, hash
-from wtforms import validators
 from flask_babel import lazy_gettext as _
 
 import flask
@@ -18,12 +17,14 @@ def pick(service_uuid):
     return flask.render_template("profile_pick.html", service=service, profiles=profiles, form=form)
 
 
+@blueprint.route("/create_quick/<service_uuid>", methods=["GET", "POST"], defaults={"quick": True}, endpoint="create_quick")
 @blueprint.route("/create_for/<service_uuid>", methods=["GET", "POST"], defaults={"create_for": True}, endpoint="create_for")
 @blueprint.route("/create/<service_uuid>", methods=["GET", "POST"])
 @security.authentication_required()
-def create(service_uuid, create_for=False):
+def create(service_uuid, create_for=False, quick=False):
     service = models.Service.query.get(service_uuid) or flask.abort(404)
     status = models.Profile.ACTIVE
+    format = formats[service.profile_format]
     # If the admin passed a user uuid, use that one, otherwise ignore it
     if create_for and flask_login.current_user.is_admin:
         user = hiboo_user.get_user(intent="profile.create_for", create_for=None)
@@ -48,22 +49,28 @@ def create(service_uuid, create_for=False):
         elif len(profiles) >= service.max_profiles or service.policy == models.Service.MANAGED:
             flask.flash(_("Your profile creation requires approval"), "warning")
             status = models.Profile.REQUEST
-    # Actually display the form
     form = forms.ProfileForm()
-    form.username.validators = formats[service.profile_format].validators()
+    form.username.validators = format.validators()
+    submit = form.validate_on_submit()
+    # If this is a quick creation, prefill the username with a generated one
+    if quick:
+        for username in format.alternatives(format.coalesce(user.username)):
+            if not service.check_username(username):
+                form.force_username(username)
+                break
+        form.hide_fields()
+    # If same_username is enabled, enforce the username
     if service.same_username:
-        form.username.data = user.username
-        form.username.render_kw = {"readonly": True}
-    if form.validate_on_submit():
-        username = user.username if service.same_username else form.username.data
-        existing = models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first()
-        if existing:
+        form.force_username(user.username)
+    # Handle the creation form
+    if submit:
+        if service.check_username(form.username.data):
             flask.flash(_("A profile with that username exists already"), "danger")
         else:
             profile = models.Profile()
-            profile.username = username
             profile.user = user
             profile.service = service
+            profile.username = form.username.data
             profile.comment = form.comment.data
             profile.status = status
             models.db.session.add(profile)
@@ -71,8 +78,9 @@ def create(service_uuid, create_for=False):
                 user=user, service=service, profile=profile)
             models.db.session.commit()
             return flask.redirect(utils.url_or_intent("account.home"))
-    return flask.render_template("profile_create.html", form=form, service=service,
-        user=user, create_for=create_for)
+    # Display either the quick version or the full version (one can switch from one to another)
+    return flask.render_template("profile_quick.html" if quick else "profile_create.html",
+        form=form, service=service, user=user, create_for=create_for)
 
 
 @blueprint.route("/claim/<service_uuid>", methods=["GET", "POST"])
diff --git a/hiboo/profile/templates/profile_quick.html b/hiboo/profile/templates/profile_quick.html
new file mode 100644
index 00000000..9de36475
--- /dev/null
+++ b/hiboo/profile/templates/profile_quick.html
@@ -0,0 +1,30 @@
+{% extends "base.html" %}
+
+{% set service_name = service.name %}
+{% set username = form.username.data %}
+
+{% block title %}{% trans %}Sign up{% endtrans %}{% endblock %}
+{% block subtitle %}
+  {% trans service_name %}for the service {{ service_name }}{% endtrans %}
+{% endblock %}
+
+{% block content %}
+<div class="row">
+    <div class="col-md-4 col-s-6 col-xs-12">
+        <div class="box box-widget widget-user-2">
+            <div class="widget-user-header bg-primary">
+            <form method="POST" class="form">
+                {{ form.hidden_tag() }}
+                <input type="hidden" name="username" value="{{ form.username.data }}">
+                <input type="submit" value="Sign up" style="opacity: 0.8" class="btn btn-lg btn-flat bg-gray text-black pull-right">
+            </form>
+            <h3 class="widget-header-username">{{ form.username.data }}</h3>
+            <h5 class="widget-header-desc">{% trans service_name %}Your new profile for {{ service_name }}{% endtrans %}</h5>
+            </div>
+            <div class="box-footer">
+            <p>{% trans service_name %}This is the first time you sign into {{ service_name }}, we first need to sign you up. You may wish to pick another username for that application. If you do, please click the "Custom profile" button.{% endtrans %}</p>                
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock %}
-- 
GitLab