Django基础项目:从零到一搭建用户管理系统

文章目录

    • 前言
    • 一、环境搭建
      • [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/

相关推荐
vvoennvv3 小时前
【Python TensorFlow】 TCN-LSTM时间序列卷积长短期记忆神经网络时序预测算法(附代码)
python·神经网络·机器学习·tensorflow·lstm·tcn
nix.gnehc3 小时前
PyTorch
人工智能·pytorch·python
z***3353 小时前
SQL Server2022版+SSMS安装教程(保姆级)
后端·python·flask
I'm Jie3 小时前
从零开始学习 TOML,配置文件的新选择
python·properties·yaml·toml
二川bro4 小时前
Scikit-learn全流程指南:Python机器学习项目实战
python·机器学习·scikit-learn
代码的乐趣4 小时前
支持selenium的chrome driver更新到142.0.7444.175
chrome·python·selenium
z樾5 小时前
TorchRL-MADDPG
pytorch·python·深度学习
Yue丶越5 小时前
【Python】基础语法入门(三)
开发语言·python