marquage textes d'aide/d'erreur (#75681) #19
|
@ -12,8 +12,11 @@ pipeline {
|
|||
always {
|
||||
script {
|
||||
utils = new Utils()
|
||||
utils.publish_coverage('coverage.xml')
|
||||
utils.publish_coverage_native('index.html')
|
||||
utils.publish_pylint('pylint.out')
|
||||
}
|
||||
mergeJunitResults()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import django
|
||||
from django import forms
|
||||
|
||||
|
||||
|
@ -23,15 +22,8 @@ class MultiSelectWidget(forms.MultiWidget):
|
|||
self.widgets.append(forms.Select(attrs=self.attrs, choices=self.choices))
|
||||
|
||||
# all subwidgets must have the same name
|
||||
if django.VERSION >= (3, 1):
|
||||
self.widgets_names = [''] * len(self.widgets)
|
||||
return super().get_context(name, value, attrs)
|
||||
else:
|
||||
context = super().get_context(name, value, attrs)
|
||||
subwidgets = context['widget']['subwidgets']
|
||||
for widget in subwidgets:
|
||||
widget['name'] = widget['name'].rsplit('_', 1)[0]
|
||||
return context
|
||||
self.widgets_names = [''] * len(self.widgets)
|
||||
return super().get_context(name, value, attrs)
|
||||
|
||||
def decompress(self, value):
|
||||
return value or []
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{% block widget-attrs %}{% endblock %}>
|
||||
{% block widget-hint %}
|
||||
{% if field.help_text %}
|
||||
<div class="hint">{{ field.help_text|safe }}</div>
|
||||
<div class="hint" id="help_text_{{field.id_for_label}}">{{ field.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block widget-control %}
|
||||
|
@ -29,7 +29,7 @@
|
|||
{% endblock %}
|
||||
{% block widget-error %}
|
||||
{% if field.errors %}
|
||||
<div class="error"><p>
|
||||
<div class="error" id="error_{{field.id_for_label}}"><p>
|
||||
{% for error in field.errors %}
|
||||
{{ error }}{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -108,6 +108,14 @@ def with_template(form):
|
|||
templates = ['gadjo/widget.html']
|
||||
if hasattr(widget, 'input_type'):
|
||||
templates.insert(0, 'gadjo/%s-widget.html' % widget.input_type)
|
||||
aria_described_by = []
|
||||
if field.field.help_text:
|
||||
aria_described_by.append(f'help_text_{field.id_for_label}')
|
||||
if field.errors:
|
||||
aria_described_by.append(f'error_{field.id_for_label}')
|
||||
field.field.widget.attrs['aria-invalid'] = 'true'
|
||||
if aria_described_by:
|
||||
field.field.widget.attrs['aria-describedby'] = ' '.join(aria_described_by)
|
||||
fields_with_templates.append(
|
||||
(
|
||||
field,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = tests.project.settings
|
|
@ -0,0 +1,10 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nocache(settings):
|
||||
settings.CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import os
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.postgresql_psycopg2'),
|
||||
'NAME': 'gadjo-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:45],
|
||||
}
|
||||
}
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [],
|
||||
'builtins': [
|
||||
'gadjo.templatetags.gadjo',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
DEBUG = True
|
||||
USE_TZ = True
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sites",
|
||||
"gadjo",
|
||||
]
|
||||
STATIC_URL = "/static/"
|
||||
SITE_ID = 1
|
||||
MIDDLEWARE_CLASSES = ()
|
||||
LOGGING = {}
|
||||
SECRET_KEY = "yay"
|
|
@ -0,0 +1,6 @@
|
|||
import gadjo.finders
|
||||
|
||||
|
||||
def test_finder():
|
||||
finder = gadjo.finders.XStaticFinder()
|
||||
assert len(finder.find(path='', all=True)) == 4
|
|
@ -0,0 +1,73 @@
|
|||
import html
|
||||
import urllib
|
||||
|
||||
from django import forms
|
||||
from django.template import Context, Template
|
||||
from django.test.client import RequestFactory
|
||||
from pyquery import PyQuery
|
||||
|
||||
|
||||
def test_start_timestamp():
|
||||
t = Template('{% start_timestamp %}')
|
||||
assert t.render(Context())
|
||||
|
||||
|
||||
def test_querystring():
|
||||
t = Template('{% querystring "name"="Ayers" "age"=20 %}')
|
||||
ctx = Context({'request': RequestFactory().get('/')})
|
||||
assert urllib.parse.parse_qs(urllib.parse.urlparse(html.unescape(t.render(ctx))).query) == {
|
||||
'age': ['20'],
|
||||
'name': ['Ayers'],
|
||||
}
|
||||
ctx = Context({'request': RequestFactory().get('/?age=10')})
|
||||
assert urllib.parse.parse_qs(urllib.parse.urlparse(html.unescape(t.render(ctx))).query) == {
|
||||
'age': ['20'],
|
||||
'name': ['Ayers'],
|
||||
}
|
||||
|
||||
t = Template('{% querystring "name"="Ayers" without "gender" %}')
|
||||
ctx = Context({'request': RequestFactory().get('/')})
|
||||
assert urllib.parse.parse_qs(urllib.parse.urlparse(html.unescape(t.render(ctx))).query) == {
|
||||
'name': ['Ayers']
|
||||
}
|
||||
ctx = Context({'request': RequestFactory().get('/?gender=male')})
|
||||
assert urllib.parse.parse_qs(urllib.parse.urlparse(html.unescape(t.render(ctx))).query) == {
|
||||
'name': ['Ayers']
|
||||
}
|
||||
|
||||
|
||||
def test_with_template():
|
||||
class ExampleForm(forms.Form):
|
||||
text = forms.CharField(label='Text', max_length=50)
|
||||
|
||||
request = RequestFactory().get('/')
|
||||
t = Template('{{ form|with_template }}')
|
||||
ctx = Context({'request': request, 'form': ExampleForm()})
|
||||
rendered = t.render(ctx)
|
||||
assert PyQuery(rendered).find('input[type=text]')
|
||||
assert not PyQuery(rendered).find('input[type=text]').attr['aria-invalid']
|
||||
|
||||
ctx = Context({'request': request, 'form': ExampleForm(data=request.GET)})
|
||||
rendered = t.render(ctx)
|
||||
assert (
|
||||
PyQuery(rendered).find('input[type=text][aria-describedby]').attr['aria-describedby']
|
||||
== 'error_id_text'
|
||||
)
|
||||
assert PyQuery(rendered).find('input[type=text]').attr['aria-invalid']
|
||||
|
||||
class ExampleForm(forms.Form):
|
||||
text = forms.CharField(label='Text', max_length=50, help_text='Help text')
|
||||
|
||||
ctx = Context({'request': request, 'form': ExampleForm()})
|
||||
rendered = t.render(ctx)
|
||||
assert (
|
||||
PyQuery(rendered).find('input[type=text][aria-describedby]').attr['aria-describedby']
|
||||
== 'help_text_id_text'
|
||||
)
|
||||
|
||||
ctx = Context({'request': request, 'form': ExampleForm(data=request.GET)})
|
||||
rendered = t.render(ctx)
|
||||
assert (
|
||||
PyQuery(rendered).find('input[type=text][aria-describedby]').attr['aria-describedby']
|
||||
== 'help_text_id_text error_id_text'
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
from django import forms
|
||||
from django.template import Context, Template
|
||||
from django.test.client import RequestFactory
|
||||
from pyquery import PyQuery
|
||||
|
||||
from gadjo.forms.widgets import MultiSelectWidget
|
||||
|
||||
|
||||
def test_multiselect_widget():
|
||||
class ExampleForm(forms.Form):
|
||||
choices = forms.MultipleChoiceField(
|
||||
label='choices', choices=[('a', 'Aa'), ('b', 'Bb'), ('c', 'Cc')], widget=MultiSelectWidget
|
||||
)
|
||||
|
||||
request = RequestFactory().get('/')
|
||||
t = Template('{{ form|with_template }}')
|
||||
ctx = Context({'request': request, 'form': ExampleForm()})
|
||||
rendered = t.render(ctx)
|
||||
assert len(PyQuery(rendered).find('select')) == 1
|
||||
assert PyQuery(rendered).find('.gadjo-multi-select-widget--button-add')
|
||||
|
||||
request = RequestFactory().get('/?choices=a&choices=b')
|
||||
t = Template('{{ form|with_template }}')
|
||||
ctx = Context({'request': request, 'form': ExampleForm(data=request.GET)})
|
||||
rendered = t.render(ctx)
|
||||
assert len(PyQuery(rendered).find('select')) == 2
|
||||
assert PyQuery(rendered).find('option[selected]').text() == 'Aa Bb'
|
||||
assert ctx['form'].cleaned_data == {'choices': ['a', 'b']}
|
17
tox.ini
17
tox.ini
|
@ -1,13 +1,28 @@
|
|||
[tox]
|
||||
envlist = codestyle-pylint
|
||||
envlist = codestyle-coverage-pylint
|
||||
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/gadjo/{env:BRANCH_NAME:}
|
||||
|
||||
[testenv]
|
||||
usedevelop =
|
||||
coverage: True
|
||||
nocoverage: False
|
||||
setenv =
|
||||
SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
JUNIT=--junitxml=junit-{envname}.xml
|
||||
coverage: COVERAGE=--cov-report xml --cov-report html --cov=gadjo/
|
||||
deps =
|
||||
pytest
|
||||
pytest-django
|
||||
WebTest
|
||||
psycopg2-binary
|
||||
psycopg2
|
||||
pyquery
|
||||
codestyle: pre-commit
|
||||
coverage: pytest-cov
|
||||
pylint: pylint
|
||||
pylint: pylint-django
|
||||
pylint: django>=3.2,<3.3
|
||||
commands =
|
||||
pytest {posargs: {env:JUNIT:} {env:COVERAGE:} tests/}
|
||||
codestyle: pre-commit run --all-files --show-diff-on-failure
|
||||
pylint: ./pylint.sh gadjo/
|
||||
|
|
Loading…
Reference in New Issue