CheckFilter/SelectFilter过滤器组件
效果图
views.py
class CheckFilter(object):
def __init__(self, name, data_list, request):
self.name = name
self.data_list = data_list
self.request = request
def __iter__(self):
for item in self.data_list:
key = str(item[0])
text = item[1]
ck = ''
# 如果url中过滤字段和循环的key相等,则默认checked为True
value_list = self.request.GET.getlist(self.name)
if key in value_list:
ck = 'checked'
value_list.remove(key)
else:
value_list.append(key)
from django.http import QueryDict
query_dict = self.request.GET.copy()
query_dict._mutable = True
query_dict.setlist(self.name, value_list)
# 如果筛选的内容不足一页
if 'page' in query_dict:
query_dict.pop('page')
param_url = query_dict.urlencode() # status=1&status=2&xx=3
if param_url:
url = '{}?{}'.format(self.request.path_info, param_url)
else:
url = self.request.path_info
tpl = '<a class="cell" href="{url}"><input type="checkbox" {ck} /><label>{text}</label></a>'
html = tpl.format(url=url, ck=ck, text=text)
yield mark_safe(html) # 生成器生成标签
class SelectFilter(object):
def __init__(self, name, data_list, request):
self.name = name
self.data_list = data_list
self.request = request
def __iter__(self):
yield mark_safe("<select class='select2' multiple='multiple' style='width: 100%;'>")
for item in self.data_list:
key = str(item[0])
text = item[1]
selected = ''
value_list = self.request.GET.getlist(self.name)
if key in value_list:
selected = 'selected'
value_list.remove(key)
else:
value_list.append(key)
query_dict = self.request.GET.copy()
query_dict._mutable = True
query_dict.setlist(self.name, value_list)
if 'page' in query_dict:
query_dict.pop('page')
param_url = query_dict.urlencode()
if param_url:
url = '{}?{}'.format(self.request.path_info, param_url)
else:
url = self.request.path_info
html = "<option value='{url}' {selected}>{text}</option>".format(url=url, selected=selected, text=text)
yield mark_safe(html)
yield mark_safe("</select>")
def issues(request, project_id):
"""创建问题、问题列表"""
if request.method == 'GET':
# 根据URL中get传参来过滤筛选
allow_filter_name = ['issues_type', 'status', 'priority', 'assign', 'attention']
condition = {}
for item in allow_filter_name:
value_list = request.GET.getlist(item)
if not value_list:
continue
condition['{}__in'.format(item)] = value_list
"""
condition = {
"status__in":[1,2],
'issues_type__in':[1,]
}
"""
form = IssuesModelForm(request)
queryset = models.Issues.objects.filter(project_id=project_id).filter(**condition)
page_obj = Pagination(current_page=request.GET.get('page'), all_count=queryset.count(),
base_url=request.path_info, query_params=request.GET, per_page=10)
issues_obj_list = queryset[page_obj.start: page_obj.end]
project_issues_type = models.IssuesType.objects.filter(project_id=project_id).values_list('id', 'title')
project_total_user = [(request.tracer.project.creator_id, request.tracer.project.creator.username), ]
project_join_user = models.ProjectUser.objects.filter(project_id=project_id).values_list('user_id',
'user__username') # 项目参与者
project_total_user.extend(project_join_user) # 项目创建者+项目参与者
invite_form = InviteFormModelForm()
context = {
'form': form,
'issues_obj_list': issues_obj_list,
'page_html': page_obj.page_html(),
'invite_form': invite_form,
'filter_list': [
# FK字段
{'title': '问题类型', 'filter': CheckFilter('issues_type', project_issues_type, request)},
# choices字段
{'title': '状态', 'filter': CheckFilter('status', models.Issues.status_choices, request)},
{'title': '优先级', 'filter': CheckFilter('priority', models.Issues.priority_choices, request)},
# select筛选
{'title': '指派者', 'filter': SelectFilter('assign', project_total_user, request)},
{'title': '关注者', 'filter': SelectFilter('attention', project_total_user, request)},
]
}
return render(request, 'manage/issues.html', context)
form = IssuesModelForm(request, data=request.POST)
if form.is_valid():
form.instance.project = request.tracer.project
form.instance.creator = request.tracer
form.save()
return JsonResponse({'status': True})
return JsonResponse({'status': False, 'errors': form.errors})
html
{% extends 'layout/manage.html' %}
{% load static %}
{% load issues %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugin/editor-md/css/editormd.min.css' %}">
<link rel="stylesheet" href="{% static 'plugin/bootstrap-datepicker/css/bootstrap-datepicker.min.css' %}">
<link rel="stylesheet" href="{% static 'plugin/bootstrap-select/css/bootstrap-select.min.css' %}">
<link rel="stylesheet" href="{% static 'plugin/select2/css/select2.min.css' %}">
<style>
.issues-list .number {
width: 100px;
text-align: right;
}
.issues-list .number a {
font-weight: 500;
padding: 0 10px;
}
.issues-list .issue .tags {
padding: 10px 0;
}
.issues-list .issue .tags span {
margin-right: 20px;
display: inline-block;
font-size: 12px;
}
.issues-list .issue .tags .type {
color: white;
padding: 1px 5px;
border-radius: 5px;
background-color: #dddddd;
}
.editormd {
margin-bottom: 0;
}
.pd-0 {
padding: 0 !important;
}
/* 筛选 */
.filter-area .item {
margin-bottom: 15px;
}
.filter-area .item .title {
padding: 5px 0;
}
.filter-area .item .check-list a {
text-decoration: none;
display: inline-block;
min-width: 65px;
}
.filter-area .item .check-list label {
font-weight: 200;
font-size: 13px;
margin-left: 3px;
}
.filter-area .item .check-list a:hover {
font-weight: 300;
}
.filter-area .item .check-list .cell {
margin-right: 10px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid clearfix" style="padding: 20px 0;">
<div class="col-sm-3">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-search" aria-hidden="true"></i> 筛选
</div>
<div class="panel-body filter-area">
{% for row in filter_list %}
<div class="item">
<div class="title">{{ row.title }}</div>
<div class="check-list">
{% for item in row.filter %}
{{ item }}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-sm-9">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-quora" aria-hidden="true"></i> 问题
</div>
<div class="panel-body">
<a class="btn btn-success btn-sm" data-toggle="modal" data-target="#addModal">新建问题</a>
<a class="btn btn-primary btn-sm" data-toggle="modal" data-target="#inviteModal">邀请成员</a>
</div>
<table class="table">
<tbody class="issues-list">
{% for item in issues_obj_list %}
<tr>
<td class="number">
<i class="fa fa-circle text-{{ item.priority }}"></i>
<a target="_blank" href="{% url 'manage:issues_detail' project_id=request.tracer.project.id issues_id=item.id %}">{% string_just item.id %}</a>
</td>
<td class="issue">
<div>
<a target="_blank"
href="{% url 'manage:issues_detail' project_id=request.tracer.project.id issues_id=item.id %}">{{ item.subject }}</a>
</div>
<div class="tags">
<span class="type">
{{ item.issues_type.title }}
</span>
<span>
<i class="fa fa-refresh" aria-hidden="true"></i>
{{ item.get_status_display }}
</span>
{% if item.assign %}
<span>
<i class="fa fa-hand-o-right" aria-hidden="true"></i>
{{ item.assign.username }}
</span>
{% endif %}
<span>
<i class="fa fa-user-o" aria-hidden="true"></i>
{{ item.creator.username }}
</span>
{% if item.end_date %}
<span><i class="fa fa-calendar"
aria-hidden="true"></i> {{ item.end_date }} 截止</span>
{% endif %}
<span><i class="fa fa-clock-o"
aria-hidden="true"></i> {{ item.latest_update_datetime }} 更新</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<nav aria-label="...">
<ul class="pagination" style="margin-top: 0;">
{{ page_html|safe }}
</ul>
</nav>
</div>
</div>
<div id="addModal" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title">新建问题</h4>
</div>
<div class="modal-body" style="padding-right: 40px;">
<form id="addForm" class="form-horizontal">
{% csrf_token %}
<!-- 问题类型 -->
<div class="form-group">
<label for="{{ form.issues_type.id_for_label }}"
class="col-md-2 control-label">{{ form.issues_type.label }}</label>
<div class="col-md-10">
<div>
<div>
{{ form.issues_type }}
</div>
<div class="error-msg"></div>
</div>
<div class="error-msg"></div>
</div>
</div>
<!-- 主题 -->
<div class="form-group">
<label for="{{ form.subject.id_for_label }}"
class="col-md-2 control-label">{{ form.subject.label }}</label>
<div class="col-md-10">
<div>
<div>
{{ form.subject }}
</div>
<div class="error-msg"></div>
</div>
<div class="error-msg"></div>
</div>
</div>
<!-- 模块 -->
<div class="form-group">
<label for="{{ form.module.id_for_label }}"
class="col-md-2 control-label">{{ form.module.label }}</label>
<div class="col-md-10">
<div>
<div>
{{ form.module }}
</div>
<div class="error-msg"></div>
</div>
<div class="error-msg"></div>
</div>
</div>
<!-- 问题描述 -->
<div class="form-group">
<label for="{{ form.desc.id_for_label }}"
class="col-md-2 control-label">{{ form.desc.label }}</label>
<div class="col-md-10">
<div>
<div id="editor">
{{ form.desc }}
</div>
<div class="error-msg"></div>
</div>
<div class="error-msg"></div>
</div>
</div>
<div class="form-group clearfix">
<!-- 状态 -->
<div class="col-md-6 pd-0">
<label for="{{ form.status.id_for_label }}"
class="col-md-4 control-label">{{ form.status.label }}</label>
<div class="col-md-8 clearfix">
<div>
{{ form.status }}
</div>
<div class="error-msg"></div>
</div>
</div>
<!-- 优先级 -->
<div class="col-md-6 pd-0">
<label for=" {{ form.priority.id_for_label }}"
class="col-md-4 control-label">{{ form.priority.label }}</label>
<div class="col-md-8">
<div>
{{ form.priority }}
</div>
<div class="error-msg"></div>
</div>
</div>
</div>
<div class="form-group clearfix">
<!-- 指派给 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label">指派给</label>
<div class="col-md-8">
{{ form.assign }}
<div class="error-msg"></div>
</div>
</div>
<!-- 关注者 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label">关注者</label>
<div class="col-md-8">
{{ form.attention }}
<div class="error-msg"></div>
</div>
</div>
</div>
<div class="form-group clearfix">
<!-- 开始时间 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label">开始时间</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-addon" id="sizing-addon2">
<i class="fa fa-calendar" aria-hidden="true"></i>
</span>
{{ form.start_date }}
</div>
<span class="error-msg"></span>
</div>
</div>
<!-- 截止时间 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label">截止时间</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-addon" id="sizing-addon2">
<i class="fa fa-calendar" aria-hidden="true"></i>
</span>
{{ form.end_date }}
</div>
<span class="error-msg"></span>
</div>
</div>
</div>
<div class="form-group clearfix">
<!-- 模式 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label">模式</label>
<div class="col-md-8">
<div>
{{ form.mode }}
</div>
<div class="error-msg"></div>
</div>
</div>
<!-- 父问题 -->
<div class="col-md-6 pd-0">
<label for="inputPassword3" class="col-md-4 control-label"> 父问题</label>
<div class="col-md-8">
{{ form.parent }}
<div class="error-msg"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
<button type="button" class="btn btn-primary" id="btnAddSubmit">添 加</button>
</div>
</div>
</div>
</div>
<div class="modal fade in" id="inviteModal" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">邀请成员</h4>
</div>
<div class="modal-body" style="padding-right: 40px;">
<form id="inviteForm">
{% csrf_token %}
{% for item in invite_form %}
<div class="form-group">
<label for="{{ item.id_for_label }}">{{ item.label }}</label>
<span>{% if item.help_text %}({{ item.help_text }}){% endif %}</span>
{{ item }}
<span class="error-msg"></span>
</div>
{% endfor %}
<button type="button" class="btn btn-success" id="btnGenInviteCode">生成邀请码</button>
</form>
<div id="inviteArea" class="hide">
<hr/>
<div class="form-group">
<div class="input-group">
<div class="input-group-btn">
<input type="button" value="邀请链接" class="btn btn-default">
</div>
<input type="text" class="form-control" id="inviteUrl">
<div class="input-group-btn">
<input type="button" value="复制链接" class="btn btn-primary" id="btnCopyUrl">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugin/editor-md/editormd.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap-select/js/bootstrap-select.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap-select/js/i18n/defaults-zh_CN.min.js' %}"></script>
<script src="{% static 'plugin/select2/js/select2.min.js' %}"></script>
<script src="{% static 'plugin/select2/js/i18n/zh-CN.js' %}"></script>
<script>
var WIKI_UPLOAD_URL = "{% url 'manage:wiki_upload' project_id=request.tracer.project.id %}";
var POST_ISSUES = "{% url 'manage:issues' project_id=request.tracer.project.id %}";
var INVITE_URL = "{% url 'manage:invite_url' project_id=request.tracer.project.id %}";
$(function () {
bindBootStrapShownEvent();
initDatePicker();
bindAddSubmit();
bindClickCheckBox();
initSelect2();
bindCreateInviteCode();
bindCopyUrl();
})
// 复制邀请码
function bindCopyUrl() {
$('#btnCopyUrl').click(function () {
var textInput = $('#inviteUrl')[0];
textInput.select();
document.execCommand('Copy');
alert('复制邀请码成功!')
})
}
// 生成邀请码
function bindCreateInviteCode() {
$('#btnGenInviteCode').click(function () {
$('.error-msg').empty()
$.ajax({
url: INVITE_URL,
type: 'POST',
data: $('#inviteForm').serialize(),
dataType: 'JSON',
success: function (res) {
if(res.status){
$('#inviteArea').removeClass('hide').find('#inviteUrl').val(res.data);
}else {
$.each(res.errors, function (k, v) {
$('#id_' + k).next('.error-msg').text(v[0]);
})
}
}
})
})
}
// 初始化select2标签
function initSelect2() {
$('.select2').select2({}).on('select2:select', function (e) {
// 选中某一项触发
{#console.log(e);#}
location.href = e.params.data.id;
}).on('select2:unselect', function (e) {
// 取消选中某一项触发
location.href = e.params.data.id;
})
}
// 点击checkbox进行筛选
function bindClickCheckBox() {
$('.filter-area').find(':checkbox').click(function () {
location.href = $(this).parent().attr('href');
})
}
// 添加问题
function bindAddSubmit() {
$('#btnAddSubmit').click(function () {
$.ajax({
url: POST_ISSUES,
type: 'POST',
data: $('#addForm').serialize(),
dataType: 'JSON',
success: function (res) {
if(res.status){
location.href = location.href;
}else {
$.each(res.errors, function (k, v) {
$('#id_' + k).parent().next('.error-msg').text(v[0]);
})
}
}
})
})
}
/*
添加对话框:初始化时间选择
*/
function initDatePicker() {
$('#id_start_date,#id_end_date').datepicker({
format: 'yyyy-mm-dd',
startDate: '0',
language: "zh-CN",
clearBtn: true, //清除按钮
autoclose: true
});
}
function bindBootStrapShownEvent() {
$('#addModal').on('shown.bs.modal', function (event) {
// 对话框弹出时,内容触发。
initEditorMd();
})
}
/*
初始化markdown编辑器(textarea转换为编辑器)
*/
function initEditorMd() {
editormd('editor', {
placeholder: "请输入内容",
height: 300,
path: "{% static 'plugin/editor-md/lib/' %}",
imageUpload: true,
imageFormats: ["jpg", 'jpeg', 'png', 'gif'],
imageUploadURL: WIKI_UPLOAD_URL
})
}
</script>
{% endblock %}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具