当前位置: 首页 > article >正文

使用 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

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

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

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

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

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

<!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攻击

2. 错误处理
   - 前端错误提示
   - 后端异常处理
   - 用户友好的错误消息

使用方法


1. 启动服务器

python manage.py runserver
 

2. 访问地址

http://127.0.0.1:8000/
 

3. 管理后台
`
http://127.0.0.1:8000/admin/
 

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


http://www.kler.cn/a/468067.html

相关文章:

  • Unity 中计算射线和平面相交距离的原理
  • 【大数据】(选修)实验4 安装熟悉HBase数据库并实践
  • Haskell语言的多线程编程
  • jenkins入门--安装jenkins
  • Hello 2025
  • NRF24L01模块STM32通信-通信初始化
  • CDN SSLTLS以及安全
  • 前端项目打包发布
  • 大话C++:第31篇 顺序容器
  • 实时路由优化 :网络性能与安全的革命性提升
  • redis解决高并发抢购
  • Go小技巧易错点100例(十九)
  • 2501d,jingo优化
  • Python如何实现与Colyseus的通信?
  • [React] 生态有哪些
  • 2024年, Milvus 社区的那些事
  • UCAS 24秋网络认证技术 CH10 SSL 复习
  • 蓝桥杯-Python
  • Colyseus 与 Cesium 集成:构建实时地理可视化应用
  • 声音是如何产生的
  • 语雀导入md文件图片丢失
  • Pytorch 三小时极限入门教程
  • [网络安全]DVWA之XSS(DOM)攻击姿势及解题详析合集
  • 111 - Lecture 6 - Objects and Classes
  • 《深度学习梯度消失问题:原因与解决之道》
  • 第9章 子程序与函数调用