data: add select2 widget for page selection in link list cell (#76059) #79
|
@ -23,12 +23,13 @@ from django.utils.translation import gettext_lazy as _
|
|||
from combo.utils import cache_during_request
|
||||
|
||||
from .models import ConfigJsonCell, LinkCell, LinkListCell, MenuCell, Page
|
||||
from .widgets import PageSelect2Widget
|
||||
|
||||
|
||||
@cache_during_request
|
||||
def get_page_choices():
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
return [(x.id, '%s %s' % ('\u00a0' * x.level * 2, x.title)) for x in pages]
|
||||
return [(x.id, '%s%s' % ('\u00a0' * x.level * 2, x.title)) for x in pages]
|
||||
|
||||
|
||||
class MenuCellForm(forms.ModelForm):
|
||||
|
@ -48,7 +49,9 @@ class LinkCellForm(forms.ModelForm):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['link_page'].widget = forms.Select(choices=[(None, '-----')] + get_page_choices())
|
||||
self.fields['link_page'].widget = PageSelect2Widget(
|
||||
self.instance.link_page, choices=[(None, '-----')] + get_page_choices()
|
||||
)
|
||||
|
||||
|
||||
class LinkCellForLinkListCellForm(LinkCellForm):
|
||||
|
|
|
@ -14,8 +14,28 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
from django.forms.widgets import Select
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class FlexSize(Select):
|
||||
template_name = 'combo/widgets/flexsize.html'
|
||||
|
||||
|
||||
class PageSelect2Widget(Select):
|
||||
min_choices = 20
|
||||
|
||||
class Media:
|
||||
js = 'xstatic/select2.min.js'
|
||||
css = {'all': ('xstatic/select2.min.css',)}
|
||||
|
||||
def __init__(self, selected_page, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if len(self.choices) > self.min_choices:
|
||||
current_choice = selected_page.id if selected_page else None
|
||||
self.choices = [x for x in self.choices if x[0] == current_choice]
|
||||
self.attrs['data-autocomplete'] = 'true'
|
||||
self.attrs['lang'] = settings.LANGUAGE_CODE
|
||||
self.attrs['data-select2-url'] = reverse('combo-manager-select2-page-choices')
|
||||
|
|
|
@ -467,6 +467,19 @@ $(function() {
|
|||
$('.cell').on('combo:cellform-reloaded', function() {
|
||||
multiSelectWidget.init(this);
|
||||
});
|
||||
|
||||
function init_select2() {
|
||||
$('select[data-autocomplete]').each(function(idx, elem) {
|
||||
$(elem).select2({
|
||||
ajax: {
|
||||
url: $(elem).data('select2-url'),
|
||||
dataType: 'json'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
init_select2();
|
||||
$('.cell').on('combo:cellform-reloaded', init_select2);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<link rel="stylesheet" type="text/css" media="all" href="{% static "xstatic/leaflet.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{% static "css/combo.map.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{% static "css/gadjo.multiselectwidget.css" %}"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{% static "xstatic/select2.min.css" %}"/>
|
||||
{% endblock %}
|
||||
{% block page-title %}{% firstof site_title "Combo" %}{% endblock %}
|
||||
{% block site-title %}{% firstof site_title "Combo" %}{% endblock %}
|
||||
|
@ -36,5 +37,7 @@
|
|||
<script src="{% static "xstatic/leaflet.js" %}"></script>
|
||||
<script src="{% static "js/combo.map.js" %}"></script>
|
||||
<script src="{% static "js/gadjo.multiselectwidget.js" %}"></script>
|
||||
<script src="{% static "xstatic/select2.min.js" %}"></script>
|
||||
<script src="{% static "xstatic/i18n/fr.js" %}"></script>
|
||||
|
||||
<script src="{% url "javascript-catalog" %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -30,6 +30,7 @@ urlpatterns = [
|
|||
re_path(r'^site-export$', pages_admin_required(views.site_export), name='combo-manager-site-export'),
|
||||
re_path(r'^site-import$', pages_admin_required(views.site_import), name='combo-manager-site-import'),
|
||||
re_path(r'^site-settings$', views.site_settings, name='combo-manager-site-settings'),
|
||||
re_path(r'^ajax-page-choices$', views.select2_page_choices, name='combo-manager-select2-page-choices'),
|
||||
re_path(
|
||||
r'^cells/invalid-report/$',
|
||||
pages_admin_required(views.invalid_cell_report),
|
||||
|
|
|
@ -18,6 +18,7 @@ import datetime
|
|||
import hashlib
|
||||
import json
|
||||
import tarfile
|
||||
import unicodedata
|
||||
from io import BytesIO
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
|
@ -1195,6 +1196,34 @@ class LinkListOrder(ManagedPageMixin, View):
|
|||
link_list_order = LinkListOrder.as_view()
|
||||
|
||||
|
||||
class Select2PageChoicesView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
search_term = request.GET.get('term', '')
|
||||
search_term = unicodedata.normalize('NFKC', search_term).casefold()
|
||||
|
||||
try:
|
||||
page_number = int(request.GET.get('page', 1))
|
||||
except ValueError:
|
||||
page_number = 1
|
||||
|
||||
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
|
||||
pages = [x for x in pages if search_term in unicodedata.normalize('NFKC', x.title).casefold()]
|
||||
vdeniaud
commented
J'ai fait au plus simple pour la partie recherche. Un autre truc simple et plus performant aurait été le filtrage en SQL via genre Sinon la meilleure approche ça serait probablement trigram + unaccent mais c'est relou à mettre en place. D'une manière ou d'une autre cette vue à vocation à devenir générique au détour d'un autre ticket qui nécessite select2 pour un champ d'un cellule. J'ai fait au plus simple pour la partie recherche.
Un autre truc simple et plus performant aurait été le filtrage en SQL via genre `__icontains`, mais la gestion des accents aurait été moins bonne.
Sinon la meilleure approche ça serait probablement trigram + unaccent mais c'est relou à mettre en place.
D'une manière ou d'une autre cette vue à vocation à devenir générique au détour d'un autre ticket qui nécessite select2 pour un champ d'un cellule.
|
||||
|
||||
options = [
|
||||
{'id': x.id, 'text': '%s%s' % ('\u00a0' * x.level * 2, x.title)}
|
||||
for x in pages[(page_number - 1) * 10 : page_number * 10]
|
||||
]
|
||||
if page_number == 1:
|
||||
options = [{'id': '', 'text': '-----'}] + options
|
||||
|
||||
return JsonResponse(
|
||||
{'results': options, 'pagination': {'more': bool(len(pages) >= page_number * 10)}}
|
||||
)
|
||||
|
||||
|
||||
select2_page_choices = Select2PageChoicesView.as_view()
|
||||
|
||||
|
||||
class SiteSettingsView(UpdateView):
|
||||
form_class = SiteSettingsForm
|
||||
template_name = 'combo/site_settings.html'
|
||||
|
|
|
@ -2779,6 +2779,87 @@ def test_link_cell_validation():
|
|||
assert form.errors['url'] == ["syntax error: Could not parse the remainder: '{test_url' from '{test_url'"]
|
||||
|
||||
|
||||
def test_link_cell_page_choices(app, admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page.objects.create(title='One', slug='one')
|
||||
page2 = Page.objects.create(title='Two', slug='two')
|
||||
cell = LinkCell.objects.create(order=1, page=page, placeholder='content')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/pages/%s/' % page.pk)
|
||||
assert 'data-autocomplete' not in resp.text
|
||||
|
||||
field_prefix = 'cdata_linkcell-%s-' % cell.id
|
||||
assert resp.form[field_prefix + 'link_page'].options == [
|
||||
('', True, '-----'),
|
||||
(str(page.pk), False, 'One'),
|
||||
(str(page2.pk), False, 'Two'),
|
||||
]
|
||||
|
||||
resp.form[field_prefix + 'link_page'] = page2.pk
|
||||
manager_submit_cell(resp.form)
|
||||
assert resp.form[field_prefix + 'link_page'].value == str(page2.pk)
|
||||
|
||||
# add many pages to enable select2 field
|
||||
for i in range(1, 21):
|
||||
Page.objects.create(title='Page %s' % i, slug=str(i))
|
||||
|
||||
manager_submit_cell(resp.form)
|
||||
assert 'data-autocomplete' in resp.text
|
||||
|
||||
# only selected option exists, other will be added by JS
|
||||
assert resp.form[field_prefix + 'link_page'].options == [(str(page2.pk), True, 'Two')]
|
||||
|
||||
new_page = Page.objects.get(slug='1')
|
||||
resp.form[field_prefix + 'link_page'].force_value(new_page.pk)
|
||||
manager_submit_cell(resp.form)
|
||||
assert resp.form[field_prefix + 'link_page'].options == [(str(new_page.pk), True, 'Page 1')]
|
||||
|
||||
resp.form[field_prefix + 'link_page'].force_value('')
|
||||
manager_submit_cell(resp.form)
|
||||
assert resp.form[field_prefix + 'link_page'].options == [('', True, '-----')]
|
||||
|
||||
resp = app.get('/manage/ajax-page-choices')
|
||||
assert len(resp.json['results']) == 11
|
||||
assert [x['text'] for x in resp.json['results'][:4]] == ['-----', 'One', 'Two', 'Page 1']
|
||||
assert resp.json['results'][-1]['text'] == 'Page 8'
|
||||
assert resp.json['pagination']['more'] is True
|
||||
|
||||
resp = app.get('/manage/ajax-page-choices?page=2')
|
||||
assert len(resp.json['results']) == 10
|
||||
assert resp.json['results'][0]['text'] == 'Page 9'
|
||||
assert resp.json['results'][-1]['text'] == 'Page 18'
|
||||
assert resp.json['pagination']['more'] is True
|
||||
|
||||
resp = app.get('/manage/ajax-page-choices?page=3')
|
||||
assert len(resp.json['results']) == 2
|
||||
assert resp.json['results'][0]['text'] == 'Page 19'
|
||||
assert resp.json['results'][-1]['text'] == 'Page 20'
|
||||
assert resp.json['pagination']['more'] is False
|
||||
|
||||
resp = app.get('/manage/ajax-page-choices?term=page')
|
||||
assert len(resp.json['results']) == 11
|
||||
assert resp.json['results'][0]['text'] == '-----'
|
||||
assert resp.json['results'][1]['text'] == 'Page 1'
|
||||
assert resp.json['results'][-1]['text'] == 'Page 10'
|
||||
assert resp.json['pagination']['more'] is True
|
||||
|
||||
resp = app.get('/manage/ajax-page-choices?term=O')
|
||||
assert len(resp.json['results']) == 3
|
||||
assert resp.json['results'][0]['text'] == '-----'
|
||||
assert resp.json['results'][1]['text'] == 'One'
|
||||
assert resp.json['results'][2]['text'] == 'Two'
|
||||
assert resp.json['pagination']['more'] is False
|
||||
|
||||
Page.objects.create(title='Child one', slug='child', parent=page)
|
||||
resp = app.get('/manage/ajax-page-choices?term=One')
|
||||
assert len(resp.json['results']) == 3
|
||||
assert resp.json['results'][0]['text'] == '-----'
|
||||
assert resp.json['results'][1]['text'] == 'One'
|
||||
assert resp.json['results'][2]['text'] == '\u00a0' * 2 + 'Child one'
|
||||
assert resp.json['pagination']['more'] is False
|
||||
|
||||
|
||||
def test_add_edit_delete_list_link_item(app, admin_user):
|
||||
Page.objects.all().delete()
|
||||
page = Page.objects.create(title='One', slug='one', template_name='standard')
|
||||
|
|
Loading…
Reference in New Issue
Ici on exploite les traductions packagées avec select2, c'est peu explicite mais nettement plus simple que de les prendre à notre charge comme dans wcs et a2.