第一步 安装项目依赖
#pip安装
Django==3.2.2
django-filter==2.4.0 # 自定义过滤字段
django-tables2==2.3.4 # 自定义表格显示字段
django-widget-tweaks==1.4.8 # 用户美化表单
第二步 创建项目和应用
先使用django-admin startproject myproject创建一个名为myproject的应用,接着使用python manage.py startappusers 创建一个名为users的app,并把它和其它三个项目依赖加入到settings.py的INSTALLED_APPS中去。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'widget_tweaks', # 项目依赖
'django_tables2',# 项目依赖
'django_filters',# 项目依赖
'users', # 自建应用
]
然后把app下的urls路径加入到项目文件夹的urls.py里去。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('myadmin/', include('users.urls'))
]
使用Django自带的User模型,无需创建模型,但考虑到后续存在切换自定义用户模型的可能性,我们使用get_user_model方法获取用户模型。
修改models.py
from django.contrib.auth import get_user_model
User = get_user_model()
第三步 编写URLs和视图
在users 目录下新建urls.py
from django.urls import path, re_path
from . import views
app_name = "users"
urlpatterns = [
path('', views.UserAdminTableView.as_view(), name='user_admin'),
path('users/create/', views.UserCreateView.as_view(), name='user_create'),
path('users/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('users/<int:pk>/delete/', views.UserDeleteView.as_view(), name='user_delete'),
]
编写与各个url路由对应的视图,它们位于user目录下views.py
# 核心Django模块,比如Mixins和通用视图
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import User
# django fitler和django_tables2提供的Mixins
from django_tables2 import SingleTableView, RequestConfig
from django_filters.views import FilterView
# filters.py定义了用哪些字段对模型进行过滤
from .filters import UserFilter
# tables.py定义了用哪些字段将在表格中展示
from .tables import UserTable
# forms.py定义了用哪些字段创建或更新用户
from .forms import UserForm
# 管理用户主界面
class UserAdminTableView(LoginRequiredMixin, SingleTableView, FilterView):
filter = None
# 使用UserFilter过滤
filter_class = UserFilter
# 使用UserTable展示数据
table_class = UserTable
template_name = 'users/user_admin.html'
# 获取过滤后的查询集
def get_queryset(self, **kwargs):
qs = User.objects.all().order_by('-id')
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
# 将查询集与table实例集合,提供filter和table两个变量前端渲染
# 每页5条记录
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
t = self.table_class(data=self.get_queryset())
RequestConfig(self.request, paginate={"per_page": 5}).configure(t)
context['filter'] = self.filter
context['table'] = t
return context
# create
class UserCreateView(LoginRequiredMixin, CreateView):
model = User
template_name = 'users/user_form.html'
form_class = UserForm
success_url = reverse_lazy('users:user_admin')
# update
class UserUpdateView(LoginRequiredMixin, UpdateView):
model = User
template_name = 'users/user_form.html'
form_class = UserForm
success_url = reverse_lazy('users:user_admin')
# Delete
class UserDeleteView(LoginRequiredMixin, DeleteView):
model = User
template_name = 'users/user_confirm_delete.html'
success_url = reverse_lazy('users:user_admin')
在视图中我们分别使用了UserFilter, UserTable和UserForm, 它们分别位于users目录下的filters.py,tables.py和 forms.py,
# users/filters.py
import django_filters
from django.db.models import Q
from .models import User
class UserFilter(django_filters.FilterSet):
'''
Filter user by username, email, is_staff, ect
'''
# 自定义过滤字段
query = django_filters.CharFilter(method='my_custom_filter', label='关键词')
def my_custom_filter(self, queryset, q, value):
return queryset.filter(Q(username__icontains=value) | Q(email__icontains=value))
class Meta:
# 使用哪个模型和哪些字段过滤
model = User
fields = {
'is_staff',
'is_superuser',
'is_active'
}
# users/tables.py
import django_tables2 as tables
from django.utils.html import format_html
from django.urls import reverse
from .models import User
class UserTable(tables.Table):
id_select = tables.CheckBoxColumn(accessor="id", orderable=False, exclude_from_export=True)
actions = tables.Column(empty_values=(), verbose_name="操作", orderable=False, exclude_from_export=True)
class Meta:
# 使用哪个模型
model = User
# 表格中显示哪些字段
fields = ['username', 'email', 'first_name', 'last_name', 'is_staff']
# 表格中字段显示顺序
sequence = ['id_select'] + fields + ['actions']
# 表格模板
template_name = "users/bs4_tables2.html"
# 表格样式
attrs = {"class": "table table-striped table-sm text-nowrap"}
# 排序字段
order_by_field = 'sort_by' # default: sort
# page_field = 'page'
# 自定义操作链接
def render_actions(self, value, record):
return format_html(
'<a class="btn btn-sm badge badge-pill badge-warning ml-2" href= "' +
reverse("users:user_update", args=[str(record.pk)]) + '">' + '编辑' + '</a>'
+ '<a class=" btn btn-sm badge badge-pill badge-danger ml-2" href= "' +
reverse("users:user_delete", args=[str(record.pk)]) + '">' + '删除' + '</a>'
)
# users/forms.py
from django import forms
from .models import User
class UserForm(forms.ModelForm):
class Meta:
# 自定义使用哪个模型和哪些字段来创建和更新用户
model = User
fields = ('username', 'email', 'password', 'first_name', 'last_name')
widgets = {
'password':forms.PasswordInput(),
}
第四步 编写模板
本项目一共使用到6个模板文件,它们位于users\templates\users目录下:
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description"
content="">
<title>Django Filter + Django Tables 2 Admin</title>
<!-- using online links -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.0/font/bootstrap-icons.css">
<style>
@import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,500,700);
body, table, th, tr, td, p
{
font-family: 'Source Sans Pro', sans-serif;
color:#666;
}
body a { color:#666 }
body a:hover { text-decoration:none;}
</style>
</head>
<body class="bg-light">
<!-- head -->
<div class="bg-secondary">
<div class="container-fluid px-0" style="max-width: 1200px;">
<!-- right-top-navbar -->
<nav class="navbar row">
<div class="col-6">
<a class="navbar-brand text-white" href="#">
大江狗Django Filter + Tables2管理后台
</a>
</div>
<!-- User profile dropdown -->
<div class="dropdown col-6 text-right">
{% if not request.user.is_authenticated %}
<small><a href="/admin/" class="btn btn-sm btn-info">Login</a></small>
{% else %}
<a class="dropdown-toggle text-white" href="#" id="dropdown-user" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ request.user.username }}
</a>
<div class="dropdown-menu dropdown-menu-right mr-2 mt-2" aria-labelledby="dropdown-user">
<a class="dropdown-item text-muted" href="/admin/logout/">Log Out</a>
</div>
{% endif %}
</div>
</nav>
</div>
</div>
<!-- End of head -->
<div class="container-fluid py-0 mt-3" style="max-width: 1200px;">
<!-- Start of x_box 1 -->
<div class="mx-md-3">
{% block content %}
{% endblock %}
</div><!-- End of x_box -->
</div>
<!-- using online scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
</body>
</html>
bs_form.html
{% load widget_tweaks %}
{% if form.non_field_errors %}
<div class="alert alert-warning alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span>
</button>
{% for error in form.non_field_errors %} {{ error }} {% endfor %}
</div>
{% endif %}
{% for hidden_field in form.hidden_fields %} {{ hidden_field }} {% endfor %}
{% for field in form.visible_fields %}
<div class="form-group{% if field.errors %} invalid{% endif %}">
{{ field.label_tag }}
{% render_field field class="form-control"%}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<p class="text-danger small">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
bs4_tables2.html
{% load django_tables2 %}
{% load i18n %}
{% block table-wrapper %}
<div class="table-wrapper mt-2">
{% block table %}
<div class="table-responsive">
<table {% render_attrs table.attrs class="table" %}>
{% block table.thead %}
{% if table.show_header %}
<thead {{ table.attrs.thead.as_html }}>
<tr>
{% for column in table.columns %}
<th {{ column.attrs.th.as_html }}>
{% if column.orderable %}
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"><i class="bi bi-chevron-expand"></i>{{ column.header }} </a>
{% else %}
{{ column.header }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
{% if table.has_footer %}
<tfoot {{ table.attrs.tfoot.as_html }}>
<tr>
{% for column in table.columns %}
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
{% endblock table.tfoot %}
</table>
</div>
{% endblock table %}
{% block pagination %}
{% if table.page and table.paginator.num_pages > 1 %}
<nav aria-label="Table navigation ">
<ul class="pagination justify-content-center float-md-left">
{% if table.page.has_previous %}
{% block pagination.previous %}
<li class="previous page-item">
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">
<span aria-hidden="true">«</span>
</a>
</li>
{% endblock pagination.previous %}
{% endif %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
{% for p in table.page|table_page_range:table.paginator %}
<li class="page-item{% if table.page.number == p %} active{% endif %}">
<a class="page-link" {% if p != '...' %}href="{% querystring table.prefixed_page_field=p %}"{% endif %}>
{{ p }}
</a>
</li>
{% endfor %}
{% endblock pagination.range %}
{% endif %}
{% if table.page.has_next %}
{% block pagination.next %}
<li class="next page-item">
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}" class="page-link">
<span aria-hidden="true">»</span>
</a>
</li>
{% endblock pagination.next %}
{% endif %}
</ul>
<div class="float-md-right d-none d-md-block">
<p>{% trans 'Per page' %}: [ <a href="{% querystring 'per_page'=20 %}">20</a> |
<a href="{% querystring 'per_page'=50 %}">50</a>
| <a href="{% querystring 'per_page'=100 %}">100</a> ]
</p>
</div>
</nav>
{% endif %}
{% endblock pagination %}
</div>
{% endblock table-wrapper %}
user_admin.html
{% extends 'users/base.html' %}
{% load django_tables2 %}
{% block content %}
<div class="align-items-center row">
<div class="col-6 pt-2">
<h5>管理用户</h5>
</div>
<div class="col-6">
<span class="dropdown float-right">
<a href="{% url 'users:user_create' %}"><button type="button" id="modal-create-btn" class="btn btn-sm btn-success"
data-toggle="tooltip" data-placement="top" title="添加用户">
<i class="bi bi-plus"></i>添加用户</button></a>
</span>
</div>
</div>
<div class="align-items-center py-1 row bg-white">
<div class="col-sm-6 col-md-3">
<form class="" action="" method="get">
<div class="input-group">
<input type="text" name="query" class="form-control search-menu"
autocomplete="off" placeholder="关键词" value="{{ request.GET.query }}">
<div class="input-group-append">
<button class="btn btn-primary" type="submit">
<i class="bi bi-search" aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
<div class="col-sm-6 col-md-9 mt-1">
<span class="dropdown float-md-right">
<button type="button" class="btn btn-sm btn-light dropdown-toggle my-0" data-toggle="dropdown" aria-expanded="false">
<i class="bi bi-funnel"></i> 过滤
</button>
<div class="dropdown-menu dropdown-menu-right px-3 mt-2" role="menu" style="width:280px">
<form class="form-horizontal" role="form" autocomplete="off">
{% include 'users/bs_form.html' with form=filter.form %}
<button type="submit" class="btn btn-sm btn-info form-control">确定</button>
</form>
</div>
<a href="{{ request.path }}" class="btn btn-light btn-sm">
<i class="bi bi-backspace-reverse"></i> 重置
</a>
</span>
</div>
</div>
{% render_table table %}
{% endblock %}
User_confirm_delete.html
{% extends 'users/base.html' %}
{% block content %}
<div class="x_title align-items-center row bg-white rounded-top">
<div class="col-6 pt-2">
<h5>删除用户</h5>
</div>
</div>
<div class="align-items-center py-1 row bg-white pl-3 pb-3 border-top border-gray rounded-bottom">
<form method="post">{% csrf_token %}
<p>你确定要删除用户"{{ user }}"吗?</p>
<input type="submit" value="Confirm" class="btn btn-warning" />
</form>
</div>
{% endblock %}
user_form.html
{% extends 'users/base.html' %}
{% block content %}
<div class="align-items-center row bg-white rounded-top">
<div class="col-6 pt-2">
<h5>{% if object %}编辑用户 {% else %} 添加用户 {% endif %}</h5>
</div>
</div>
<div class="align-items-center py-1 row bg-white pl-3 pb-3 border-top border-gray rounded-bottom">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% include 'users/bs_form.html' with form=form %}
<p><input type="submit" class="btn btn-success" value="Submit"></p>
</form>
</div>
{% endblock %}
第五步 查看效果
连续运行如下命令,
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
登录/admin/添加一些用户,在访问127.0.0.0.1:8000/myadmin




