文章目录
-
- 前言
- 一、环境搭建
-
- [1.1 项目结构](#1.1 项目结构)
- [1.2 创建项目和应用](#1.2 创建项目和应用)
- [1.3 安装依赖](#1.3 安装依赖)
- [1.4 配置数据库](#1.4 配置数据库)
- 二、代码文件
-
- [2.1 user_management/settings.py (完整配置)](#2.1 user_management/settings.py (完整配置))
- [2.2 users/models.py](#2.2 users/models.py)
- [2.3 users/views.py](#2.3 users/views.py)
- [2.4 users/urls.py](#2.4 users/urls.py)
- [2.5 user_management/urls.py](#2.5 user_management/urls.py)
- [2.6 users/templates/users/index.html](#2.6 users/templates/users/index.html)
- [2.7 项目运行](#2.7 项目运行)
前言
项目打开页面截图如下:

系统特点:
- ORM功能强大:自动处理SQL注入,提供丰富的查询API
- CSRF保护:内置安全机制,防止跨站请求伪造
- Admin后台:可快速生成管理界面
- 模板系统:安全且灵活的模板引擎
- 完整CRUD:创建、读取、更新、删除用户
- 实时搜索:即时过滤用户列表
- 数据统计:总用户数、平均年龄、今日新增
- 时间记录:自动记录创建和更新时间
- 数据验证:前后端双重验证,确保数据完整性
-
- 邮箱唯一性:防止重复注册
一、环境搭建
1.1 项目结构
user_management/
├── manage.py
├── user_management/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── users/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── views.py
├── urls.py
└── templates/
└── users/
└── index.html
1.2 创建项目和应用
bash
# 创建Django项目
django-admin startproject user_management
cd user_management
# 创建应用
python manage.py startapp users
1.3 安装依赖
bash
pip install django mysqlclient
1.4 配置数据库
在 user_management/settings.py 中修改数据库配置:
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'user_management_db',
'USER': 'root',
'PASSWORD': 'your_password', # 请修改为你的MySQL密码
'HOST': 'localhost',
'PORT': '3306',
}
}
如果没有创建数据库,则需要先创建数据库
sql
CREATE DATABASE IF NOT EXISTS user_management_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
二、代码文件
2.1 user_management/settings.py (完整配置)
python
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-your-secret-key-here'
DEBUG = True
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users', # 添加我们的应用
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'user_management.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'user_management.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'user_management_db',
'USER': 'root',
'PASSWORD': 'your_password', # 请修改为你的MySQL密码
'HOST': 'localhost',
'PORT': '3306',
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
2.2 users/models.py
python
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
email = models.EmailField(unique=True, verbose_name='邮箱')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '用户'
verbose_name_plural = '用户'
ordering = ['-id']
def __str__(self):
return self.name
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'age': self.age,
'email': self.email,
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
}
2.3 users/views.py
python
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
import json
from .models import User
def index(request):
return render(request, 'users/index.html')
@require_http_methods(["GET"])
def get_users(request):
search = request.GET.get('search', '')
users = User.objects.all()
if search:
users = users.filter(
models.Q(name__icontains=search) |
models.Q(email__icontains=search)
)
users_list = [user.to_dict() for user in users.order_by('-id')]
return JsonResponse({'users': users_list})
@csrf_exempt
@require_http_methods(["POST"])
def add_user(request):
try:
data = json.loads(request.body)
if not all([data.get('name'), data.get('age'), data.get('email')]):
return JsonResponse({'error': '所有字段都是必需的'}, status=400)
# 检查邮箱是否已存在
if User.objects.filter(email=data['email']).exists():
return JsonResponse({'error': '邮箱已存在'}, status=400)
user = User.objects.create(
name=data['name'],
age=data['age'],
email=data['email']
)
return JsonResponse({'success': True, 'message': '用户添加成功'})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@csrf_exempt
@require_http_methods(["PUT"])
def update_user(request, user_id):
try:
data = json.loads(request.body)
if not all([data.get('name'), data.get('age'), data.get('email')]):
return JsonResponse({'error': '所有字段都是必需的'}, status=400)
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return JsonResponse({'error': '用户不存在'}, status=404)
# 检查邮箱是否已被其他用户使用
if User.objects.filter(email=data['email']).exclude(id=user_id).exists():
return JsonResponse({'error': '邮箱已被其他用户使用'}, status=400)
user.name = data['name']
user.age = data['age']
user.email = data['email']
user.save()
return JsonResponse({'success': True, 'message': '用户更新成功'})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@csrf_exempt
@require_http_methods(["DELETE"])
def delete_user(request, user_id):
try:
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return JsonResponse({'error': '用户不存在'}, status=404)
user.delete()
return JsonResponse({'success': True, 'message': '用户删除成功'})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
2.4 users/urls.py
python
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('api/users/', views.get_users, name='get_users'),
path('api/users/add/', views.add_user, name='add_user'),
path('api/users/<int:user_id>/update/', views.update_user, name='update_user'),
path('api/users/<int:user_id>/delete/', views.delete_user, name='delete_user'),
]
2.5 user_management/urls.py
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('users.urls')),
]
2.6 users/templates/users/index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django 用户管理系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 25px 50px rgba(0,0,0,0.15);
overflow: hidden;
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 30s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.header h1 {
font-size: 2.8em;
margin-bottom: 10px;
position: relative;
z-index: 1;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
position: relative;
z-index: 1;
}
.controls {
padding: 30px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
.stats {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
flex: 1;
background: white;
padding: 20px;
border-radius: 15px;
text-align: center;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
transition: transform 0.3s;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-number {
font-size: 2em;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
color: #6c757d;
margin-top: 5px;
}
.controls-row {
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
.search-box {
flex: 1;
min-width: 250px;
position: relative;
}
.search-box input {
width: 100%;
padding: 15px 20px 15px 50px;
border: 2px solid #e9ecef;
border-radius: 30px;
font-size: 16px;
transition: all 0.3s;
background: white;
}
.search-box::before {
content: '🔍';
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.btn {
padding: 15px 35px;
border: none;
border-radius: 30px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
.table-container {
padding: 30px;
max-height: 600px;
overflow-y: auto;
}
.table-container::-webkit-scrollbar {
width: 8px;
}
.table-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.table-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.table-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
th, td {
padding: 18px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #495057;
position: sticky;
top: 0;
z-index: 10;
}
tbody tr {
transition: all 0.3s;
}
tbody tr:hover {
background: #f8f9fa;
transform: scale(1.01);
}
.action-buttons {
display: flex;
gap: 10px;
}
.btn-sm {
padding: 8px 18px;
font-size: 14px;
border-radius: 20px;
border: none;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.btn-edit {
background: #28a745;
color: white;
}
.btn-edit:hover {
background: #218838;
transform: translateY(-1px);
}
.btn-delete {
background: #dc3545;
color: white;
}
.btn-delete:hover {
background: #c82333;
transform: translateY(-1px);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1000;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 40px;
border-radius: 20px;
width: 90%;
max-width: 500px;
box-shadow: 0 25px 50px rgba(0,0,0,0.3);
animation: slideDown 0.3s;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translate(-50%, -60%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.modal-header {
margin-bottom: 30px;
text-align: center;
}
.modal-header h2 {
color: #333;
font-size: 1.8em;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
}
.form-group input {
width: 100%;
padding: 12px 20px;
border: 2px solid #e9ecef;
border-radius: 10px;
font-size: 16px;
transition: all 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.modal-footer {
display: flex;
gap: 15px;
justify-content: flex-end;
margin-top: 30px;
}
.btn-cancel {
background: #6c757d;
color: white;
}
.btn-cancel:hover {
background: #5a6268;
}
.message {
padding: 18px 25px;
margin: 20px 30px;
border-radius: 12px;
display: none;
animation: slideIn 0.3s;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.message.success {
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
color: #0c5460;
}
.message.error {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: #721c24;
}
.empty-state {
text-align: center;
padding: 80px 20px;
color: #6c757d;
}
.empty-state svg {
width: 120px;
height: 120px;
margin-bottom: 25px;
opacity: 0.4;
}
.empty-state h3 {
font-size: 1.5em;
margin-bottom: 10px;
}
.loading {
text-align: center;
padding: 40px;
color: #6c757d;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Django 用户管理系统</h1>
<p>基于 Django ORM 的智能化用户管理平台</p>
</div>
<div id="message" class="message"></div>
<div class="controls">
<div class="stats">
<div class="stat-card">
<div class="stat-number" id="totalUsers">0</div>
<div class="stat-label">总用户数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="avgAge">0</div>
<div class="stat-label">平均年龄</div>
</div>
<div class="stat-card">
<div class="stat-number" id="todayUsers">0</div>
<div class="stat-label">今日新增</div>
</div>
</div>
<div class="controls-row">
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索姓名或邮箱...">
</div>
<button class="btn btn-primary" onclick="openAddModal()">
<span>➕</span> 新增用户
</button>
</div>
</div>
<div class="table-container">
<div id="loading" class="loading" style="display: none;">
<div class="spinner"></div>
<p>加载中...</p>
</div>
<table id="userTable" style="display: none;">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>邮箱</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="userTableBody">
</tbody>
</table>
<div id="emptyState" class="empty-state" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<h3>暂无用户数据</h3>
<p>点击"新增用户"按钮添加第一个用户</p>
</div>
</div>
</div>
<!-- 用户表单模态框 -->
<div id="userModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">新增用户</h2>
</div>
<form id="userForm">
{% csrf_token %}
<div class="form-group">
<label for="name">姓名 *</label>
<input type="text" id="name" name="name" required placeholder="请输入姓名">
</div>
<div class="form-group">
<label for="age">年龄 *</label>
<input type="number" id="age" name="age" min="1" max="150" required placeholder="请输入年龄">
</div>
<div class="form-group">
<label for="email">邮箱 *</label>
<input type="email" id="email" name="email" required placeholder="请输入邮箱">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-cancel" onclick="closeModal()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
<script>
let currentEditId = null;
let allUsers = [];
// 获取CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// 页面加载时获取用户列表
document.addEventListener('DOMContentLoaded', function() {
loadUsers();
// 搜索功能
document.getElementById('searchInput').addEventListener('input', function() {
filterUsers(this.value);
});
// 表单提交
document.getElementById('userForm').addEventListener('submit', function(e) {
e.preventDefault();
saveUser();
});
});
// 加载用户列表
async function loadUsers() {
showLoading(true);
try {
const response = await fetch('/api/users/');
const data = await response.json();
allUsers = data.users || [];
displayUsers(allUsers);
updateStats();
} catch (error) {
showMessage('加载用户列表失败', 'error');
} finally {
showLoading(false);
}
}
// 显示加载状态
function showLoading(show) {
const loading = document.getElementById('loading');
const table = document.getElementById('userTable');
const emptyState = document.getElementById('emptyState');
if (show) {
loading.style.display = 'block';
table.style.display = 'none';
emptyState.style.display = 'none';
} else {
loading.style.display = 'none';
table.style.display = 'table';
}
}
// 显示用户列表
function displayUsers(users) {
const tbody = document.getElementById('userTableBody');
const emptyState = document.getElementById('emptyState');
if (users.length === 0) {
tbody.innerHTML = '';
emptyState.style.display = 'block';
document.getElementById('userTable').style.display = 'none';
} else {
emptyState.style.display = 'none';
document.getElementById('userTable').style.display = 'table';
tbody.innerHTML = users.map(user => `
<tr>
<td><strong>#${user.id}</strong></td>
<td>${user.name}</td>
<td>${user.age} 岁</td>
<td>${user.email}</td>
<td>${formatDate(user.created_at)}</td>
<td>
<div class="action-buttons">
<button class="btn-sm btn-edit" onclick="editUser(${user.id})">✏️ 编辑</button>
<button class="btn-sm btn-delete" onclick="deleteUser(${user.id})">🗑️ 删除</button>
</div>
</td>
</tr>
`).join('');
}
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
}
// 过滤用户
function filterUsers(search) {
if (!search) {
displayUsers(allUsers);
return;
}
const filtered = allUsers.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
displayUsers(filtered);
}
// 更新统计信息
function updateStats() {
document.getElementById('totalUsers').textContent = allUsers.length;
if (allUsers.length > 0) {
const avgAge = Math.round(allUsers.reduce((sum, user) => sum + user.age, 0) / allUsers.length);
document.getElementById('avgAge').textContent = avgAge;
// 计算今日新增
const today = new Date().toDateString();
const todayUsers = allUsers.filter(user =>
new Date(user.created_at).toDateString() === today
).length;
document.getElementById('todayUsers').textContent = todayUsers;
} else {
document.getElementById('avgAge').textContent = '0';
document.getElementById('todayUsers').textContent = '0';
}
}
// 打开新增模态框
function openAddModal() {
currentEditId = null;
document.getElementById('modalTitle').textContent = '新增用户';
document.getElementById('userForm').reset();
document.getElementById('userModal').style.display = 'block';
}
// 编辑用户
function editUser(id) {
const user = allUsers.find(u => u.id === id);
if (user) {
currentEditId = id;
document.getElementById('modalTitle').textContent = '编辑用户';
document.getElementById('name').value = user.name;
document.getElementById('age').value = user.age;
document.getElementById('email').value = user.email;
document.getElementById('userModal').style.display = 'block';
}
}
// 保存用户
async function saveUser() {
const formData = {
name: document.getElementById('name').value.trim(),
age: parseInt(document.getElementById('age').value),
email: document.getElementById('email').value.trim()
};
try {
let url, method;
if (currentEditId) {
url = `/api/users/${currentEditId}/update/`;
method = 'PUT';
} else {
url = '/api/users/add/';
method = 'POST';
}
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (response.ok) {
showMessage(result.message, 'success');
closeModal();
loadUsers();
} else {
showMessage(result.error || '操作失败', 'error');
}
} catch (error) {
showMessage('网络错误,请稍后重试', 'error');
}
}
// 删除用户
async function deleteUser(id) {
const user = allUsers.find(u => u.id === id);
if (!confirm(`确定要删除用户 "${user.name}" 吗?`)) {
return;
}
try {
const response = await fetch(`/api/users/${id}/delete/`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrftoken
}
});
const result = await response.json();
if (response.ok) {
showMessage(result.message, 'success');
loadUsers();
} else {
showMessage(result.error || '删除失败', 'error');
}
} catch (error) {
showMessage('网络错误,请稍后重试', 'error');
}
}
// 关闭模态框
function closeModal() {
document.getElementById('userModal').style.display = 'none';
document.getElementById('userForm').reset();
currentEditId = null;
}
// 显示消息
function showMessage(text, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
}, 4000);
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById('userModal');
if (event.target === modal) {
closeModal();
}
}
// ESC键关闭模态框
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
</body>
</html>
2.7 项目运行
1、数据库迁移
bash
python manage.py makemigrations
python manage.py migrate
2、运行开发服务器
bash
python manage.py runserver
3、访问系统
打开浏览器访问 http://127.0.0.1:8000/