code serveur pour la validation à la volée (#76632) #241

Merged
fpeters merged 1 commits from wip/76632-error-live-check into main 2023-04-18 14:28:39 +02:00
Owner
No description provided.
fpeters added 10 commits 2023-04-14 10:08:00 +02:00
fpeters force-pushed wip/76632-error-live-check from 1dff943bc8 to 5157128421 2023-04-14 10:59:16 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 5157128421 to 5d91ca6e95 2023-04-14 11:11:03 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 5d91ca6e95 to 6e1c78d9e1 2023-04-14 13:15:29 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 6e1c78d9e1 to 4ffc69908c 2023-04-14 15:04:54 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 4ffc69908c to e442cfba16 2023-04-14 15:34:07 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from e442cfba16 to 6af0c162f8 2023-04-14 16:13:00 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 6af0c162f8 to 6d432eeaa1 2023-04-14 16:14:21 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 6d432eeaa1 to 21a8f4af13 2023-04-14 16:18:40 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 21a8f4af13 to 740bc5ebea 2023-04-14 16:22:33 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 740bc5ebea to 205117eea4 2023-04-16 10:41:28 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 205117eea4 to 79e34bbb27 2023-04-16 11:04:05 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from 79e34bbb27 to f5ed99c0d1 2023-04-16 11:50:33 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from f5ed99c0d1 to fbf9ddcab1 2023-04-16 15:41:58 +02:00 Compare
fpeters force-pushed wip/76632-error-live-check from fbf9ddcab1 to cf2920d0de 2023-04-17 09:42:04 +02:00 Compare
fpeters reviewed 2023-04-17 10:02:23 +02:00
@ -2357,3 +2357,3 @@
assert len(rsps.calls) == 1
assert '?xxx=FOO%20BAR%2030' in rsps.calls[-1].request.url
assert 'invalid value' not in resp
assert len(resp.pyquery('.error:not(#form_error_fieldname)')) == 0
Author
Owner

Il y a toute une série de changements de ce type dans les tests, c'est parce que l'HTML incorpore désormais un <template> avec dedans une classe "error", qui se trouvait donc matchée.

Il y a toute une série de changements de ce type dans les tests, c'est parce que l'HTML incorpore désormais un `<template>` avec dedans une classe "error", qui se trouvait donc matchée.
@ -538,3 +538,3 @@
formdef.data_class().wipe()
next_page = page.forms[0].submit('submit') # but the field is required
assert next_page.pyquery('div.error').text() == 'required field'
assert next_page.pyquery('#form_error_f0').text() == 'required field'
Author
Owner

Ou pour la même raison, plutôt que viser le div.error, viser le div du champ précis.

Ou pour la même raison, plutôt que viser le div.error, viser le div du champ précis.
@ -1974,1 +1974,4 @@
assert live_resp.json['result']['3']['content'] == '<p>Xbar {{ form_var_foo }}Ybar plopZ</p>'
def test_field_live_validation(pub):
Author
Owner

C'est ici les vrais nouveaux tests.

C'est ici les vrais nouveaux tests.
wcs/fields.py Outdated
@ -876,6 +876,7 @@ class WidgetField(Field):
prefill = {}
widget_class = None
widget_supports_live_validation = True
Author
Owner

attribut pour déterminer qu'un champ gère la validation à la volée, ça permet derrière d'exclure les champs dépréciés type tableaux, exclus du périmètre.

attribut pour déterminer qu'un champ gère la validation à la volée, ça permet derrière d'exclure les champs dépréciés type tableaux, exclus du périmètre.
wcs/fields.py Outdated
@ -1639,6 +1641,7 @@ class FileField(WidgetField):
key = 'file'
description = _('File Upload')
allow_complex = True
widget_supports_live_validation = False
Author
Owner

Les champs fichier aussi sont exclus mais c'est parce que la validation a déjà lieu lors du chargement du fichier.

Les champs fichier aussi sont exclus mais c'est parce que la validation a déjà lieu lors du chargement du fichier.
@ -940,6 +949,16 @@ class FormPage(FormdefDirectoryBase, FormTemplateMixin):
def create_form(self, *args, **kwargs):
form = self.formdef.create_form(*args, **kwargs)
form.attrs['data-live-url'] = self.formdef.get_url(language=get_publisher().current_language) + 'live'
form.attrs['data-live-validation-url'] = (
Author
Owner

C'est la présence de cet attribut qui détermine la prise en charge, ou pas, de la validation à la volée. (un moment j'ai hésité à partager data-live-url pour également y faire la validation, mais non).

C'est la présence de cet attribut qui détermine la prise en charge, ou pas, de la validation à la volée. (un moment j'ai hésité à partager data-live-url pour également y faire la validation, mais non).
@ -1670,0 +1702,4 @@
if not field_ref:
return result_error('missing ?field parameter')
parts = field_ref.split('__')
Author
Owner

En paramètre c'est ?field={{widget.get_name_for_id}} qui est une méthode qui fournit le nom avec les $ (invalides dans les id) remplacés par des __, ici on fait la conversion inverse.

En paramètre c'est ?field={{widget.get_name_for_id}} qui est une méthode qui fournit le nom avec les $ (invalides dans les id) remplacés par des `__`, ici on fait la conversion inverse.
@ -1670,0 +1703,4 @@
return result_error('missing ?field parameter')
parts = field_ref.split('__')
if len(parts) not in (1, 3):
Author
Owner

S'il y a un morceau, c'est un champ normal, s'il y a trois morceaux, c'est un champ d'un bloc (f0$element0$f1).

S'il y a un morceau, c'est un champ normal, s'il y a trois morceaux, c'est un champ d'un bloc (f0$element0$f1).
@ -1670,0 +1721,4 @@
form = Form()
widget = field.add_to_form(form)
error = widget.get_error()
Author
Owner

On crée un formulaire avec uniquement le champ qui nous intéresse, c'est pour ça que juste au-dessus pour le champ d'un bloc on modifie l'id, pour faire comme si c'était un champ direct.

On gagne du temps ici à ne pas créer de formdata temporaire etc. c'est possible parce que les validations sur les champs sont limitées aux données de celui-ci.

On crée un formulaire avec uniquement le champ qui nous intéresse, c'est pour ça que juste au-dessus pour le champ d'un bloc on modifie l'id, pour faire comme si c'était un champ direct. On gagne du temps ici à ne pas créer de formdata temporaire etc. c'est possible parce que les validations sur les champs sont limitées aux données de celui-ci.
@ -1670,0 +1726,4 @@
resp = {'err': 1, 'msg': str(error)}
if hasattr(widget, 'error_code'):
error_message = ErrorMessage(widget.error_code, '')
resp[error_message.camel_code()] = True
Author
Owner

On répond ici avec un format qui mime ce que l'API de validation javascript propose.

On répond ici avec un format qui mime ce que l'API de validation javascript propose.
@ -181,0 +185,4 @@
self.message = message
def camel_code(self):
return ''.join(x.lower() if i == 0 else x.capitalize() for i, x in enumerate(self.code.split('_')))
Author
Owner

C'est pour faire value_missing -> valueMissing, pour correspondre à l'API javascript.

C'est pour faire value_missing -> valueMissing, pour correspondre à l'API javascript.
@ -189,0 +223,4 @@
Widget.get_value_missing_message = lambda x: Widget.REQUIRED_ERROR
Widget.get_invalid_value_message = lambda x: _('Invalid value')
Widget.set_error = widget_set_error
Widget.set_error_code = widget_set_error_code
Author
Owner

Introduction de cette série de méthodes sur Widget, on est toujours à monkeypatcher ainsi la classe de base dans quixote (il y aurait #5790 pour revoir ça).

Introduction de cette série de méthodes sur Widget, on est toujours à monkeypatcher ainsi la classe de base dans quixote (il y aurait #5790 pour revoir ça).
@ -585,3 +623,3 @@
self.value = self.value.strip()
if self.maxlength and len(self.value) > self.maxlength:
self.error = _('Too long, value must be at most %d characters.') % self.maxlength
self.set_error_code('too_long')
Author
Owner

Pour les codes connus de l'API javascript on les fournit comme ça.

Pour les codes connus de l'API javascript on les fournit comme ça.
@ -593,0 +635,4 @@
def get_error_message_codes(self):
yield from super().get_error_message_codes()
if self.maxlength:
yield 'too_long'
Author
Owner

Et les messages et la liste des messages possibles (qui se trouveront posés en <template>) sont définis ici.

Et les messages et la liste des messages possibles (qui se trouveront posés en `<template>`) sont définis ici.
@ -881,3 +939,3 @@
token = get_session().add_tempfile(self.get('file'), storage=self.storage)['token']
except UploadStorageError:
self.error = _('failed to store file (system error)')
self.set_error(_('failed to store file (system error)'))
Author
Owner

Il y a des endroits où error était positionné directement, comme set_error() fait désormais un peu plus, on les change pour bien passer partout par set_error.

Il y a des endroits où error était positionné directement, comme set_error() fait désormais un peu plus, on les change pour bien passer partout par set_error.
fpeters changed title from WIP: code serveur pour la validation à la volée (#76632) to code serveur pour la validation à la volée (#76632) 2023-04-17 10:02:49 +02:00
fpeters added 1 commit 2023-04-17 13:18:49 +02:00
fpeters force-pushed wip/76632-error-live-check from cbbf9b8556 to a4a7b4c97f 2023-04-18 11:22:21 +02:00 Compare
fpeters reviewed 2023-04-18 11:29:12 +02:00
@ -181,0 +210,4 @@
# always add codes that may be generated by javascript validation API
yield 'value_missing'
yield 'bad_input'
yield 'type_mismatch'
Author
Owner

Il y a typeMismatch qui est généré en sortie d'un champ date dont le contenu est partiel (type j'ai juste mis le jour) et en regardant https://developer.mozilla.org/en-US/docs/Web/API/ValidityState j'ai vu badInput et j'ai décidé de remplacer ce que j'avais nommé "invalid_value" par "bad_input".

Il y a typeMismatch qui est généré en sortie d'un champ date dont le contenu est partiel (type j'ai juste mis le jour) et en regardant https://developer.mozilla.org/en-US/docs/Web/API/ValidityState j'ai vu badInput et j'ai décidé de remplacer ce que j'avais nommé "invalid_value" par "bad_input".
lguerin approved these changes 2023-04-18 14:19:24 +02:00
fpeters merged commit a4a7b4c97f into main 2023-04-18 14:28:39 +02:00
fpeters deleted branch wip/76632-error-live-check 2023-04-18 14:28:39 +02:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: entrouvert/wcs#241
No description provided.