使用 Axios、原生 JavaScript 和 Django 5 的搭建一个简单前后端博客系统

Django 命令集:

pip install django

django-admin startproject blog

cd blog

python manage.py startapp app

python manage.py makemigrations

python manage.py migrate

python manage.py createsuperuser

python manage.py runserver

目录结构:

代码:

app/models.py

python 复制代码
from django.db import models
from django.utils import timezone

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return self.title

app/urls.py

python 复制代码
from django.urls import path
from . import views

urlpatterns = [
    path('api/posts/', views.post_list, name='post_list'),
    path('api/posts/<int:post_id>/', views.post_detail, name='post_detail'),
]

app/views.py

python 复制代码
from django.http import JsonResponse
from .models import Post
from django.views.decorators.csrf import csrf_exempt
import json
from django.shortcuts import get_object_or_404

@csrf_exempt
def post_list(request):
    if request.method == 'GET':
        try:
            posts = Post.objects.all().order_by('-created_at')[:50]  # 限制返回最新的50篇文章
            data = [{
                'id': post.id,
                'title': post.title,
                'content': post.content,
                'created_at': post.created_at.strftime('%Y-%m-%d %H:%M')
            } for post in posts]
            return JsonResponse({'posts': data})
        except Exception as e:
            return JsonResponse({'error': f'获取文章失败: {str(e)}'}, status=500)
    
    elif request.method == 'POST':
        try:
            data = json.loads(request.body)
            title = data.get('title', '').strip()
            content = data.get('content', '').strip()
            
            if not title:
                return JsonResponse({'error': '标题不能为空'}, status=400)
            if not content:
                return JsonResponse({'error': '内容不能为空'}, status=400)
            if len(title) > 200:
                return JsonResponse({'error': '标题长度不能超过200个字符'}, status=400)
            
            post = Post.objects.create(
                title=title,
                content=content
            )
            return JsonResponse({
                'id': post.id,
                'title': post.title,
                'content': post.content,
                'created_at': post.created_at.strftime('%Y-%m-%d %H:%M')
            })
        except json.JSONDecodeError:
            return JsonResponse({'error': '无效的JSON数据'}, status=400)
        except Exception as e:
            return JsonResponse({'error': f'创建文章失败: {str(e)}'}, status=500)
    
    return JsonResponse({'error': '不支持的HTTP方法'}, status=405)

@csrf_exempt
def post_detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    
    if request.method == 'PUT':
        try:
            data = json.loads(request.body)
            title = data.get('title', '').strip()
            content = data.get('content', '').strip()
            
            if not title:
                return JsonResponse({'error': '标题不能为空'}, status=400)
            if not content:
                return JsonResponse({'error': '内容不能为空'}, status=400)
            if len(title) > 200:
                return JsonResponse({'error': '标题长度不能超过200个字符'}, status=400)
            
            post.title = title
            post.content = content
            post.save()
            
            return JsonResponse({
                'id': post.id,
                'title': post.title,
                'content': post.content,
                'created_at': post.created_at.strftime('%Y-%m-%d %H:%M')
            })
        except json.JSONDecodeError:
            return JsonResponse({'error': '无效的JSON数据'}, status=400)
        except Exception as e:
            return JsonResponse({'error': f'更新文章失败: {str(e)}'}, status=500)
    
    elif request.method == 'DELETE':
        try:
            post.delete()
            return JsonResponse({'message': '文章已删除'})
        except Exception as e:
            return JsonResponse({'error': f'删除文章失败: {str(e)}'}, status=500)
    
    return JsonResponse({'error': '不支持的HTTP方法'}, status=405)

blog/settings.py

python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',
]


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        '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',
            ],
        },
    },
]



STATIC_URL = 'static/'
STATICFILES_DIRS = [
    BASE_DIR / 'static',
]

blog/urls.py

python 复制代码
from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', TemplateView.as_view(template_name='index.html'), name='home'),
    path('', include('app.urls')),
]

根目录\static\js\axios.min.js

下载axios.min.js到static/js目录

curl https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js > blog/static/js/axios.min.js


记住在开发服务器运行时,Django会自动处理静态文件的服务。但在生产环境中,你需要运行 python manage.py collectstatic 并配置你的Web服务器来提供这些静态文件。

如果你不想下载文件,也可以考虑使用其他可靠的CDN源,比如:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
或者<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>

根目录\templates\index.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>博客系统</title>
    <style>
        body {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            font-family: Arial, sans-serif;
        }
        
        #new-post {
            background: #f5f5f5;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 30px;
        }
        
        input[type="text"], textarea {
            width: 100%;
            padding: 8px;
            margin: 10px 0;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
        }
        
        textarea {
            height: 150px;
            resize: vertical;
        }
        
        button {
            background: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        
        button:hover {
            background: #45a049;
        }
        
        .post {
            border: 1px solid #ddd;
            margin: 15px 0;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .post h3 {
            margin-top: 0;
            color: #333;
        }
        
        .post-meta {
            color: #666;
            font-size: 0.9em;
            margin-top: 15px;
        }
        
        .error-message {
            color: red;
            margin: 10px 0;
            display: none;
        }
        
        .post-actions {
            margin-top: 10px;
        }
        
        .post-actions button {
            background: #666;
            margin-right: 10px;
            font-size: 0.9em;
            padding: 5px 10px;
        }
        
        .post-actions button.delete {
            background: #dc3545;
        }
        
        .post-actions button:hover {
            opacity: 0.8;
        }
        
        .edit-form {
            display: none;
            margin-top: 15px;
            padding-top: 15px;
            border-top: 1px solid #ddd;
        }
    </style>
</head>
<body>
    <div id="new-post">
        <h2>发布新文章</h2>
        <div id="error-message" class="error-message"></div>
        <input type="text" id="title" placeholder="请输入标题">
        <textarea id="content" placeholder="请输入文章内容"></textarea>
        <button onclick="createPost()">发布文章</button>
    </div>
    
    <div id="posts"></div>

    <script src="/static/js/axios.min.js"></script>
    <script>
        // 获取所有文章
        async function getPosts() {
            try {
                const response = await axios.get('/api/posts/');
                const postsDiv = document.getElementById('posts');
                postsDiv.innerHTML = '';
                
                if (response.data.posts.length === 0) {
                    postsDiv.innerHTML = '<p style="text-align: center; color: #666;">还没有任何文章,快来发布第一篇吧!</p>';
                    return;
                }
                
                response.data.posts.forEach(post => {
                    postsDiv.innerHTML += `
                        <div class="post" id="post-${post.id}">
                            <h3>${escapeHtml(post.title)}</h3>
                            <div class="post-content">${formatContent(post.content)}</div>
                            <div class="post-meta">
                                发布时间:${post.created_at}
                            </div>
                            <div class="post-actions">
                                <button onclick="showEditForm(${post.id}, '${escapeHtml(post.title)}', '${escapeHtml(post.content)}')">编辑</button>
                                <button class="delete" onclick="deletePost(${post.id})">删除</button>
                            </div>
                            <div class="edit-form" id="edit-form-${post.id}">
                                <input type="text" id="edit-title-${post.id}" value="${escapeHtml(post.title)}">
                                <textarea id="edit-content-${post.id}">${escapeHtml(post.content)}</textarea>
                                <button onclick="updatePost(${post.id})">保存修改</button>
                                <button onclick="hideEditForm(${post.id})" style="background: #666;">取消</button>
                            </div>
                        </div>
                    `;
                });
            } catch (error) {
                showError('获取文章失败: ' + error.message);
            }
        }

        // 创建新文章
        async function createPost() {
            const title = document.getElementById('title').value.trim();
            const content = document.getElementById('content').value.trim();
            
            if (!title || !content) {
                showError('标题和内容不能为空!');
                return;
            }
            
            try {
                await axios.post('/api/posts/', {
                    title: title,
                    content: content
                });
                
                // 清空输入框
                document.getElementById('title').value = '';
                document.getElementById('content').value = '';
                document.getElementById('error-message').style.display = 'none';
                
                // 重新加载文章列表
                getPosts();
            } catch (error) {
                showError('发布文章失败: ' + error.message);
            }
        }

        // 显示错误信息
        function showError(message) {
            const errorDiv = document.getElementById('error-message');
            errorDiv.textContent = message;
            errorDiv.style.display = 'block';
        }

        // HTML转义
        function escapeHtml(unsafe) {
            return unsafe
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;")
                .replace(/"/g, "&quot;")
                .replace(/'/g, "&#039;");
        }

        // 格式化文章内容
        function formatContent(content) {
            return escapeHtml(content).replace(/\n/g, '<br>');
        }

        // 显示编辑表单
        function showEditForm(postId, title, content) {
            const editForm = document.getElementById(`edit-form-${postId}`);
            editForm.style.display = 'block';
        }
        
        // 隐藏编辑表单
        function hideEditForm(postId) {
            const editForm = document.getElementById(`edit-form-${postId}`);
            editForm.style.display = 'none';
        }
        
        // 更新文章
        async function updatePost(postId) {
            const title = document.getElementById(`edit-title-${postId}`).value.trim();
            const content = document.getElementById(`edit-content-${postId}`).value.trim();
            
            if (!title || !content) {
                showError('标题和内容不能为空!');
                return;
            }
            
            try {
                await axios.put(`/api/posts/${postId}/`, {
                    title: title,
                    content: content
                });
                
                hideEditForm(postId);
                getPosts();  // 重新加载文章列表
            } catch (error) {
                showError('更新文章失败: ' + error.message);
            }
        }
        
        // 删除文章
        async function deletePost(postId) {
            if (!confirm('确定要删除这篇文章吗?')) {
                return;
            }
            
            try {
                await axios.delete(`/api/posts/${postId}/`);
                getPosts();  // 重新加载文章列表
            } catch (error) {
                showError('删除文章失败: ' + error.message);
            }
        }

        // 页面加载时获取文章
        window.onload = getPosts;
    </script>
</body>
</html> 

程序说明:

这是一个基于Django和前端JavaScript实现的简单博客系统。为你详细说明:

系统功能

  1. 文章管理
  • 发布新文章

  • 查看文章列表

  • 编辑已有文章

  • 删除文章

技术架构

  1. 后端(Django):

```python

主要文件结构

blog/

├── app/ # Django应用

│ ├── models.py # 数据模型

│ ├── views.py # 视图函数

│ └── urls.py # URL路由

└── templates/ # 模板文件

└── index.html # 主页面

```
2. 前端:

  • HTML + CSS:页面布局和样式

  • JavaScript:使用axios处理API请求

  • RESTful API:与后端交互

API接口说明

  1. 获取文章列表

```

GET /api/posts/

返回:最新的50篇文章列表

```
2. 创建新文章

```

POST /api/posts/

数据:{title: "标题", content: "内容"}

```
3. 更新文章

```

PUT /api/posts/<post_id>/

数据:{title: "新标题", content: "新内容"}

```
4. 删除文章

```

DELETE /api/posts/<post_id>/

```

主要功能实现

  1. 文章展示

```javascript
async function getPosts() {

// 获取并展示所有文章

// 包含标题、内容、发布时间

}

```
2. 发布文章

```javascript
async function createPost() {

// 获取输入内容

// 验证数据

// 发送到服务器

// 刷新文章列表

}

```
3. 编辑文章

```javascript
async function updatePost(postId) {

// 获取修改后的内容

// 验证数据

// 发送到服务器

// 刷新文章列表

}

```
4. 删除文章

```javascript
async function deletePost(postId) {

// 确认删除

// 发送删除请求

// 刷新文章列表

}

```

安全特性

  1. 输入验证
  • 标题和内容不能为空

  • 标题长度限制

  • HTML转义防止XSS攻击

  1. 错误处理
  • 前端错误提示

  • 后端异常处理

  • 用户友好的错误消息

使用方法

  1. 启动服务器

python manage.py runserver

  1. 访问地址

http://127.0.0.1:8000/

  1. 管理后台

`

http://127.0.0.1:8000/admin/

这个系统虽然简单,但包含了基本的CRUD(创建、读取、更新、删除)功能,并且注重用户体验和安全性。适合作为学习Django和前端交互的示例项目。

相关推荐
SsummerC31 分钟前
【leetcode100】二叉树的直径
数据结构·python·算法·leetcode
Code花园37 分钟前
Bash语言的文件操作
开发语言·后端·golang
-Bin1 小时前
client-go中watch机制的一些陷阱
开发语言·后端·golang
EviaHp1 小时前
递归构建树菜单节点
java·spring boot·后端·maven·idea
半桶水专家1 小时前
Go语言中变量的作用域详解
开发语言·后端·golang
RacheV+TNY2642781 小时前
未来趋势:电商平台API接口的智能化与自动化发展
网络·人工智能·python·自动化·api
写点什么呢1 小时前
Pytorch学习12_最大池化的使用
人工智能·pytorch·python·深度学习·学习·pycharm
机构师1 小时前
<rust>在rust中,实现32位浮点数与16进制之间的转换
开发语言·后端·rust
郝开1 小时前
Python中的可变对象与不可变对象;Python中的六大标准数据类型哪些属于可变对象,哪些属于不可变对象
开发语言·python·可变对象·不可变对象
这里有鱼汤2 小时前
Python高手都在用的10个高级技巧,你会几个?
python