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 timezoneclass 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 viewsurlpatterns = [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 = titlepost.content = contentpost.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 TemplateViewurlpatterns = [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和前端交互的示例项目。