Skip to content

Commit

Permalink
refactor: form model attributes coercion
Browse files Browse the repository at this point in the history
  • Loading branch information
azmeuk committed Nov 22, 2023
1 parent e622567 commit b6cfa15
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 12 deletions.
15 changes: 15 additions & 0 deletions canaille/app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re

import wtforms.validators
from canaille.app import models
from canaille.app.i18n import DEFAULT_LANGUAGE_CODE
from canaille.app.i18n import gettext as _
from canaille.app.i18n import locale_selector
Expand Down Expand Up @@ -249,3 +250,17 @@ def set_writable(field):
field.validators = [
v for v in field.validators if not isinstance(v, wtforms.validators.ReadOnly)
]


class IDToModel:
model = None

def __init__(self, model_name):
self.model_name = model_name

def __call__(self, data):
model = getattr(models, self.model_name)
instance = data if isinstance(data, model) else model.get(id=data)
if not instance:
raise wtforms.ValidationError()
return instance
3 changes: 3 additions & 0 deletions canaille/backends/ldap/ldapobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ def __repr__(self):
else "<LDAPOBject>"
)

def __html__(self):
return self.id

def __eq__(self, other):
ldap_attributes = self.may() + self.must()
if not (
Expand Down
3 changes: 3 additions & 0 deletions canaille/backends/memory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ def __setattr__(self, name, value):
else:
super().__setattr__(name, value)

def __html__(self):
return self.id

@property
def identifier(self):
return getattr(self, self.identifier_attribute)
Expand Down
9 changes: 5 additions & 4 deletions canaille/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from canaille.app.flask import request_is_htmx
from canaille.app.flask import smtp_needed
from canaille.app.flask import user_needed
from canaille.app.forms import IDToModel
from canaille.app.forms import is_readonly
from canaille.app.forms import set_readonly
from canaille.app.forms import set_writable
Expand Down Expand Up @@ -197,7 +198,6 @@ class RegistrationPayload(VerificationPayload):
@permissions_needed("manage_users")
def user_invitation(user):
form = InvitationForm(request.form or None)

email_sent = None
registration_url = None
form_validated = False
Expand All @@ -208,7 +208,7 @@ def user_invitation(user):
form.user_name.data,
form.user_name_editable.data,
form.email.data,
form.groups.data,
[group.id for group in form.groups.data],
)
registration_url = url_for(
"core.account.registration",
Expand Down Expand Up @@ -282,7 +282,7 @@ def registration(data=None, hash=None):
data = {
"user_name": payload.user_name,
"emails": [payload.email],
"groups": payload.groups,
"groups": [models.Group.get(id=group_id) for group_id in payload.groups],
}

has_smtp = "SMTP" in current_app.config
Expand All @@ -295,7 +295,8 @@ def registration(data=None, hash=None):
if "groups" not in form and payload and payload.groups:
form["groups"] = wtforms.SelectMultipleField(
_("Groups"),
choices=[(group.id, group.display_name) for group in models.Group.query()],
choices=[(group, group.display_name) for group in models.Group.query()],
coerce=IDToModel("Group"),
)
set_readonly(form["groups"])
form.process(CombinedMultiDict((request.files, request.form)) or None, data=data)
Expand Down
11 changes: 5 additions & 6 deletions canaille/core/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from canaille.app.forms import DateTimeUTCField
from canaille.app.forms import email_validator
from canaille.app.forms import Form
from canaille.app.forms import IDToModel
from canaille.app.forms import is_uri
from canaille.app.forms import phone_number
from canaille.app.forms import set_readonly
Expand Down Expand Up @@ -273,10 +274,9 @@ def available_language_choices():
),
groups=wtforms.SelectMultipleField(
_("Groups"),
choices=lambda: [
(group.id, group.display_name) for group in models.Group.query()
],
choices=lambda: [(group, group.display_name) for group in models.Group.query()],
render_kw={"placeholder": _("users, admins …")},
coerce=IDToModel("Group"),
),
)

Expand Down Expand Up @@ -390,10 +390,9 @@ class InvitationForm(Form):
)
groups = wtforms.SelectMultipleField(
_("Groups"),
choices=lambda: [
(group.id, group.display_name) for group in models.Group.query()
],
choices=lambda: [(group, group.display_name) for group in models.Group.query()],
render_kw={},
coerce=IDToModel("Group"),
)


Expand Down
2 changes: 1 addition & 1 deletion canaille/oidc/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def client_edit(client):
software_version=form["software_version"].data,
jwk=form["jwk"].data,
jwks_uri=form["jwks_uri"].data,
audience=[models.Client.get(id=id) for id in form["audience"].data],
audience=form["audience"].data,
preconsent=form["preconsent"].data,
)
client.save()
Expand Down
4 changes: 3 additions & 1 deletion canaille/oidc/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from canaille.app import models
from canaille.app.forms import email_validator
from canaille.app.forms import Form
from canaille.app.forms import IDToModel
from canaille.app.forms import is_uri
from canaille.app.forms import unique_values
from canaille.app.i18n import lazy_gettext as _
Expand All @@ -16,7 +17,7 @@ class LogoutForm(Form):


def client_audiences():
return [(client.id, client.client_name) for client in models.Client.query()]
return [(client, client.client_name) for client in models.Client.query()]


class ClientAddForm(Form):
Expand Down Expand Up @@ -107,6 +108,7 @@ class ClientAddForm(Form):
validators=[wtforms.validators.Optional()],
choices=client_audiences,
validate_choice=False,
coerce=IDToModel("Client"),
)
logo_uri = wtforms.URLField(
_("Logo URI"),
Expand Down
8 changes: 8 additions & 0 deletions tests/core/test_profile_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,11 @@ def test_account_limit_values(
user = models.User.get(id=user.id)
assert user.lock_date == expiration_datetime
assert not user.locked


def test_edition_invalid_group(testclient, logged_admin, user, foo_group):
res = testclient.get("/profile/user/settings", status=200)
res.form["groups"].force_value("invalid")
res = res.form.submit(name="action", value="edit-settings")
assert res.flashes == [("error", "Profile edition failed.")]
res.mustcontain("Invalid choice(s): one or more data inputs could not be coerced.")

0 comments on commit b6cfa15

Please sign in to comment.