Django - Javascript动态内联FormSet与自动完成 [英] Django - Javascript dynamic inline FormSet with autocomplete
问题描述
模型
我想尝试制作一种调度程序事件编辑器,可以附加参与者。 $ _ code> class Session(models.Model):
start_time = models.DateTimeField()
end_time = models.DateTimeField()
类参与(models.Model):
session = models.ForeignKey(Session)
Particip = models.ForeignKey(User)
status = models.CharField(max_length = 1,choices = STATUSES)
在编辑器中,我想要一个自动完成搜索输入,我可以从中找到用户
添加到会话
预览
这里我输入了laurent,我'通过点击其中一个结果名称添加一个人
参与者的颜色取决于他们的状态
我有一个表单定义了 Session
对象开始&结束时间
现在我想我应该有一个内嵌格式集 参与
s
问题
- 您是否建议我为参与者使用内联表单集 / li>
- 如何动态地添加/删除参与者行?
这个问题似乎很简单,但是正确的回答将涉及几个答案。
我将使用jQuery逐一给出我的解决方案。
自动填充
这是简单的部分。您可以使用像 select2 或 jqueryui autocomplete 和一个查找用户,如
def search_users(request):
search = request.GET.get('term')
users = User.objects.filter(
Q(first_name__icontains = search)
| Q(last_name__icontains = search)
)
ulist = list({'id':u.id,'value':u'%s%s'%(u.first_name,u.last_name)}
for u in user)
return JsonResponse(ulist)
此视图与默认的jQuery UI自动填充插件兼容
动态格式集
这是棘手的一个关键是利用 management_form
和 form.DELETE
。这是我的解决方案:
- 为参与者使用内联表单(含一个额外的表格)
- 打印
management_form
- 通过克隆隐藏的空表单(额外的一个)添加自动完成选择后,使用jQuery添加表单行,并增加
id_form-TOTAL_FORMS
- 通过隐藏它们并检查隐藏的删除复选框来删除具有jQuery的表单行
<
模板
< form method = post> {%csrf_token%}
{{sessionform}}
< div>
{{participant_formset.management_form}}
< label for =part_search>搜索:< / label>< input id =part_search/>
< ul id =participation_set>
{%for members_formset%}中的tform
{{tform.id}}
< li>
< span class =参与者>
{{tform.participant}} {{tform.instance.participant.name}}
< / span>
< span class =status> {{tform.status}}< / span>
< span class =delete ui-icon ui-icon-circle-minus>
{{tform.DELETE}}
< / span>
< / li>
{%endfor%}
< / ul>
< / div>
< / form>
CSS
/ *删除按钮* /
#participation_set .delete {
display:inline-block;
vertical-align:middle;
cursor:pointer;
}
/ *隐藏删除复选框* /
#participation_set。删除输入{
display:none;
}
/ *删除表单* /
#participation_set li.deleted {
display:none;
}
/ *最后一个隐藏的表单克隆* /
#participation_set li:last-child {
display:none;
}
jQuery
/ *!这将添加一个表单行
*调用自动完成选择
* /
函数add_aform(inst,item){
if($(':input [name $ = ] [value ='+ item.id +']')。length){
return false;
}
var total = $('#id_'+ inst +'-TOTAL_FORMS')。val();
var sul ='#'+ inst;
var li = $(sul +'li:last-child');
var new_li = li.clone()。appendTo(sul);
li.find('span.participant')。append(item.label);
li.find(':input [name $ =participant]')。val(item.id);
new_li.find(':input')。each(function(){
var new_name = $(this).attr('name')
.replace(' - '+总共1)+' - ',' - '+总+' - ');
$(this).attr('name',new_name);
});
new_li.find('label')。each(function(){
var tmp = $(this).attr('for')
.replace(' - '+ - 1)+' - ',' - '+ total +' - ');
$(this).attr('for',new_for);
});
new_li.find('。delete')。点击(del_aform);
$('#id_'+ inst +'-TOTAL_FORMS')。val(++ total);
}
/ *!
* /
函数del_aform(){
$(this).parents('利 ')addClass(' 删除');
$(this).find(':checkbox')。attr('checked',true);
}
我知道我也可以使用 empty_form
实例,并使用
__前缀__
替换ids,它简化了JavaScript以提供更好的可维护性,但是我没有找到一种将代码分解为真实的方法表单和空的。
查看
该视图是非常标准的,使用 inlineformset_factory
与 extra
设置为1(以获得唯一的隐藏表单来克隆)。还不要忘记使用参与者
的
HiddenInput
小部件I'm trying to make a kind of scheduler event editor with the ability to attach participants.
Models
class Session(models.Model):
start_time = models.DateTimeField()
end_time = models.DateTimeField()
class Participation(models.Model):
session = models.ForeignKey(Session)
participant = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUSES)
In the editor I'd like to have an autocomplete search input from which I can find user
s to add to the session
Preview
Here I have typed "laurent" and I'm going to add a person by clicking on one of the resulting names
Participant colors depend on their status
I have a form for the Session
object defined with start & end times
Now I think I should have an inline formset for Participation
s
Questions
- Do you suggest that I use an inline formset for the participants ?
- How can I dynamically add/delete participant rows ?
The question seems very simple but a proper response would involve several answers.
I will give my solutions point by point, using jQuery.
Autocomplete
This is the simple part. You can use a plugin like select2 or jqueryui autocomplete and a view that finds users like
def search_users(request):
search = request.GET.get('term')
users = User.objects.filter(
Q(first_name__icontains=search)
| Q(last_name__icontains=search)
)
ulist = list({'id': u.id, 'value': u'%s %s' % (u.first_name, u.last_name)}
for u in users)
return JsonResponse(ulist)
This view is compatible with the default jQuery UI Autocomplete plugin
Dynamic Formset
This is the tricky one. The key is to take advantage of management_form
and form.DELETE
. Here is my solution:
- Use an inline formset for the participants (with one extra form)
- Print the
management_form
- Add form lines with jQuery after autocomplete selection by cloning a hidden empty form (the extra one) and incrementing
id_form-TOTAL_FORMS
- Delete form lines with jQuery by hiding them and checking a hidden delete checkbox
Template
<form method="post">{% csrf_token %}
{{ sessionform }}
<div>
{{ participant_formset.management_form }}
<label for="part_search">Search: </label><input id="part_search" />
<ul id="participation_set">
{% for tform in participant_formset %}
{{ tform.id }}
<li>
<span class="participant">
{{ tform.participant }}{{ tform.instance.participant.name }}
</span>
<span class="status">{{ tform.status }}</span>
<span class="delete ui-icon ui-icon-circle-minus">
{{ tform.DELETE }}
</span>
</li>
{% endfor %}
</ul>
</div>
</form>
CSS
/* Delete button */
#participation_set .delete {
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
/* Hidden delete checkbox */
#participation_set .delete input {
display: none;
}
/* Deleted form */
#participation_set li.deleted {
display: none;
}
/* Last hidden form to clone */
#participation_set li:last-child {
display: none;
}
jQuery
/*! This adds a form line
* Call it on autocomplete select
*/
function add_aform(inst, item) {
if ($(':input[name$="participant"][value=' + item.id + ']').length) {
return false;
}
var total = $('#id_' + inst + '-TOTAL_FORMS').val();
var sul = '#' + inst;
var li = $(sul + ' li:last-child');
var new_li = li.clone().appendTo(sul);
li.find('span.participant').append(item.label);
li.find(':input[name$="participant"]').val(item.id);
new_li.find(':input').each(function () {
var new_name = $(this).attr('name')
.replace('-' + (total - 1) + '-', '-' + total + '-');
$(this).attr('name', new_name);
});
new_li.find('label').each(function () {
var tmp = $(this).attr('for')
.replace('-' + (total - 1) + '-', '-' + total + '-');
$(this).attr('for', new_for);
});
new_li.find('.delete').click(del_aform);
$('#id_' + inst + '-TOTAL_FORMS').val(++total);
}
/*! This removes a form line
* Call it on click from delete buttons (placed inside each li)
*/
function del_aform() {
$(this).parents('li').addClass('deleted');
$(this).find(':checkbox').attr('checked', true);
}
I know I could also use an empty_form
instance and use __prefix__
to replace the ids which simplifies the javascript for a better maintainability, but I didn't find a way to factorize the code between the true form and the empty one.
View
The view is pretty standard using inlineformset_factory
with extra
set to 1 (to get the only hidden form to clone). Also don't forget to use a HiddenInput
widget for the field participant
这篇关于Django - Javascript动态内联FormSet与自动完成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!