Tom-Select as Django Widget
Tom-Select as Django Widget
我想使用 tom-select 作为 Django 自动完成 select-widget。
在对应的数据库table中(Runners)有几百行,所以我无法全部加载到html页面中。
在模型 RaceTeam
中,我有一个指向 Runner
的外键。
select 小部件将在整个页面和 html 片段中使用(通过 htmx)。
我尝试了子类 django.forms.Select
,但这会将所有数据库行吸入 widget.choices
。
我使用这个解决方案:
我把tom-selectJS+CSS复制到static/relayrace/vendor/.
class RunnerSelectWidget(Select):
class Media:
css = {
'all': ('relayrace/vendor/tom-select.css',)
}
js = ('relayrace/vendor/tom-select.complete.min.js',)
def set_choices(self, value):
# there can be several thousand choices. We don't want them here
self._choices = []
def get_choices(self):
choices = list(self._choices)
return choices
choices = property(get_choices, set_choices)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['attrs']['class']='runner-select'
return context
def optgroups(self, name, value, attrs=None):
# Make the initial value available
self._choices = [(id, runner_to_select_text(Runner.objects.get(pk=id))) for id in value]
return super().optgroups(name, value, attrs)
def runner_select_json(request):
# You might want to add some permission checking here. Otherwise everybody
# can get data via this endpoint.
query = request.GET.get('q', '').strip()
qs = get_queryset(query)
items = [dict(value=r.pk, text=runner_to_select_text(r)) for r in qs]
return JsonResponse(dict(items=items, total_count=len(items)))
def runner_to_select_text(runner):
return f'{runner.user.first_name} {runner.user.last_name}'
def get_queryset(query):
if not query:
return Runner.objects.none()
return Runner.objects.filter(Q(user__username__icontains=query)|
Q(user__first_name__icontains=query)|
Q(user__last_name__icontains=query)).order_by('user__username')
小部件的使用:
class RaceTeamForm(forms.ModelForm):
class Meta:
model = RaceTeam
widgets = {
'leader': RunnerSelectWidget(),
}
在整个页面加载和片段加载时初始化小部件的 JS:
htmx.onLoad(function(elt) {
var allSelects = htmx.findAll(elt, ".runner-select")
for( select of allSelects ) {
new TomSelect(select, {
// fetch remote data
load: function(query, callback) {
var url = '/runner_select_json?q=' + encodeURIComponent(query);
fetch(url)
.then(response => response.json())
.then(json => {
callback(json.items);
}).catch(()=>{
callback();
});
}
})}});
urls.py
url('runner_select_json', runner_select_json, name='runner_select_json')
我想使用 tom-select 作为 Django 自动完成 select-widget。
在对应的数据库table中(Runners)有几百行,所以我无法全部加载到html页面中。
在模型 RaceTeam
中,我有一个指向 Runner
的外键。
select 小部件将在整个页面和 html 片段中使用(通过 htmx)。
我尝试了子类 django.forms.Select
,但这会将所有数据库行吸入 widget.choices
。
我使用这个解决方案:
我把tom-selectJS+CSS复制到static/relayrace/vendor/.
class RunnerSelectWidget(Select):
class Media:
css = {
'all': ('relayrace/vendor/tom-select.css',)
}
js = ('relayrace/vendor/tom-select.complete.min.js',)
def set_choices(self, value):
# there can be several thousand choices. We don't want them here
self._choices = []
def get_choices(self):
choices = list(self._choices)
return choices
choices = property(get_choices, set_choices)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['attrs']['class']='runner-select'
return context
def optgroups(self, name, value, attrs=None):
# Make the initial value available
self._choices = [(id, runner_to_select_text(Runner.objects.get(pk=id))) for id in value]
return super().optgroups(name, value, attrs)
def runner_select_json(request):
# You might want to add some permission checking here. Otherwise everybody
# can get data via this endpoint.
query = request.GET.get('q', '').strip()
qs = get_queryset(query)
items = [dict(value=r.pk, text=runner_to_select_text(r)) for r in qs]
return JsonResponse(dict(items=items, total_count=len(items)))
def runner_to_select_text(runner):
return f'{runner.user.first_name} {runner.user.last_name}'
def get_queryset(query):
if not query:
return Runner.objects.none()
return Runner.objects.filter(Q(user__username__icontains=query)|
Q(user__first_name__icontains=query)|
Q(user__last_name__icontains=query)).order_by('user__username')
小部件的使用:
class RaceTeamForm(forms.ModelForm):
class Meta:
model = RaceTeam
widgets = {
'leader': RunnerSelectWidget(),
}
在整个页面加载和片段加载时初始化小部件的 JS:
htmx.onLoad(function(elt) {
var allSelects = htmx.findAll(elt, ".runner-select")
for( select of allSelects ) {
new TomSelect(select, {
// fetch remote data
load: function(query, callback) {
var url = '/runner_select_json?q=' + encodeURIComponent(query);
fetch(url)
.then(response => response.json())
.then(json => {
callback(json.items);
}).catch(()=>{
callback();
});
}
})}});
urls.py
url('runner_select_json', runner_select_json, name='runner_select_json')