使用 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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// 格式化文章内容
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和前端交互的示例项目。