python疯狂练习60天——第29天Python Web开发 - Flask和Django框架

B站影视 港台电影 2025-10-08 00:44 1

摘要:我们继续第29天的学习,主题是Python Web开发。我们将学习使用Flask和Django框架来构建Web应用程序。由于内容较多,我们将分为两部分:Flask轻量级框架和Django全功能框架。

我们继续第29天的学习,主题是Python Web开发。我们将学习使用Flask和Django框架来构建Web应用程序。
由于内容较多,我们将分为两部分:Flask轻量级框架和Django全功能框架。

今天的学习目标:

学习Flask框架,构建一个简单的Web应用学习Django框架,创建一个完整的项目

我们将通过实例来学习,包括路由、模板、表单、数据库等。

首先安装必要的库:

pip install flask flask-sqlalchemy flask-wtf flask-login flask-bootstrapfrom flask import Flask, render_template, request, redirect, url_for, flash, jsonify, sessionfrom flask_sqlalchemy import SQLAlchemyfrom flask_wtf import FlaskFormfrom wtforms import StringField, PasswordField, TextAreaField, SelectField, DecimalFieldfrom wtforms.validators import DataRequired, Email, Lengthfrom flask_Bootstrap import Bootstrapfrom datetime import datetimeimport osfrom werkzeug.security import generate_password_hash, check_password_hash# 初始化Flask应用app = Flask(__name__)app.config['SECRET_KEY'] = 'your-secret-key-here'app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False# 初始化扩展db = SQLAlchemy(app)Bootstrap(app)# 数据模型class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(128)) created_at = db.Column(db.DateTime, default=datetime.utcnow) posts = db.relationship('Post', backref='author', lazy=True) comments = db.relationship('Comment', backref='author', lazy=True) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password)class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) category = db.Column(db.String(50), default='General') comments = db.relationship('Comment', backref='post', lazy=True, cascade='all, delete-orphan')class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)# 表单类class RegistrationForm(FlaskForm): username = StringField('用户名', validators=[DataRequired, Length(min=4, max=80)]) email = StringField('邮箱', validators=[DataRequired, Email]) password = PasswordField('密码', validators=[DataRequired, Length(min=6)])class LoginForm(FlaskForm): username = StringField('用户名', validators=[DataRequired]) password = PasswordField('密码', validators=[DataRequired])class PostForm(FlaskForm): title = StringField('标题', validators=[DataRequired, Length(max=200)]) content = TextAreaField('内容', validators=[DataRequired]) category = SelectField('分类', choices=[ ('General', '通用'), ('Technology', '技术'), ('Life', '生活'), ('Travel', '旅行'), ('Food', '美食') ])class CommentForm(FlaskForm): content = TextAreaField('评论', validators=[DataRequired])# 创建数据库表with app.app_context: db.create_all# 路由和视图函数@app.route('/')def index: """首页""" page = request.args.get('page', 1, type=int) posts = Post.query.order_by(Post.created_at.desc).paginate( page=page, per_page=5, error_out=False ) return render_template('index.html', posts=posts)@app.route('/about')def about: """关于页面""" return render_template('about.html')@app.route('/register', methods=['GET', 'POST'])def register: """用户注册""" form = RegistrationForm if form.validate_on_submit: # 检查用户名和邮箱是否已存在 existing_user = User.query.filter( (User.username == form.username.data) | (User.email == form.email.data) ).first if existing_user: flash('用户名或邮箱已存在', 'danger') return render_template('register.html', form=form) # 创建新用户 user = User(username=form.username.data, email=form.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit flash('注册成功!请登录。', 'success') return redirect(url_for('login')) return render_template('register.html', form=form)@app.route('/login', methods=['GET', 'POST'])def login: """用户登录""" form = LoginForm if form.validate_on_submit: user = User.query.filter_by(username=form.username.data).first if user and user.check_password(form.password.data): session['user_id'] = user.id session['username'] = user.username flash(f'欢迎回来,{user.username}!', 'success') return redirect(url_for('index')) else: flash('用户名或密码错误', 'danger') return render_template('login.html', form=form)@app.route('/logout')def logout: """用户登出""" session.clear flash('您已成功登出', 'info') return redirect(url_for('index'))@app.route('/post/new', methods=['GET', 'POST'])def new_post: """创建新文章""" if 'user_id' not in session: flash('请先登录', 'warning') return redirect(url_for('login')) form = PostForm if form.validate_on_submit: post = Post( title=form.title.data, content=form.content.data, category=form.category.data, user_id=session['user_id'] ) db.session.add(post) db.session.commit flash('文章发布成功!', 'success') return redirect(url_for('index')) return render_template('create_post.html', form=form)@app.route('/post/')def post_detail(post_id): """文章详情""" post = Post.query.get_or_404(post_id) form = CommentForm return render_template('post_detail.html', post=post, form=form)@app.route('/post//comment', methods=['POST'])def add_comment(post_id): """添加评论""" if 'user_id' not in session: flash('请先登录', 'warning') return redirect(url_for('login')) form = CommentForm if form.validate_on_submit: comment = Comment( content=form.content.data, user_id=session['user_id'], post_id=post_id ) db.session.add(comment) db.session.commit flash('评论发布成功!', 'success') return redirect(url_for('post_detail', post_id=post_id))@app.route('/profile')def profile: """用户个人资料""" if 'user_id' not in session: flash('请先登录', 'warning') return redirect(url_for('login')) user = User.query.get(session['user_id']) user_posts = Post.query.filter_by(user_id=user.id).order_by(Post.created_at.desc).all return render_template('profile.html', user=user, posts=user_posts)@app.route('/api/posts')def api_posts: """API接口:获取文章列表""" posts = Post.query.order_by(Post.created_at.desc).limit(10).all posts_data = for post in posts: posts_data.append({ 'id': post.id, 'title': post.title, 'content': post.content[:100] + '...' if len(post.content) > 100 else post.content, 'author': post.author.username, 'created_at': post.created_at.isoformat, 'category': post.category }) return jsonify(posts_data)# 错误处理@app.errorhandler(404)def not_found_error(error): return render_template('404.html'), 404@app.errorhandler(500)def internal_error(error): db.session.rollback return render_template('500.html'), 500if __name__ == '__main__': app.run(debug=True)

创建templates文件夹,并添加以下模板文件:

(基础模板)

{% block title %}我的博客{% endblock %} .navbar-brand { font-weight: bold; } .post-card { transition: transform 0.2s; } .post-card:hover { transform: translateY(-2px); } .category-badge { font-size: 0.8em; } 我的博客 首页 关于 {% if session.username %} 写文章 {{ session.username }} 个人资料
退出 {% else %} 登录 注册 {% endif %} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} {{ message }} {% endfor %} {% endif %} {% endwith %} {% block content %}{% endblock %}

© 2024 我的博客. 使用 Flask 构建.

{% block scripts %}{% endblock %}

(首页)

{% extends "base.html" %}{% block title %}首页 - 我的博客{% endblock %}{% block content %}

最新文章

{% for post in posts.items %}
{{ post.title }}
{{ post.category }}

{{ post.content|striptags|truncate(200) }}

{{ post.author.username }} {{ post.created_at.strftime('%Y-%m-%d %H:%M') }} {{ post.comments|length }} 评论 阅读更多 {% else %} 还没有文章,点击这里 创建第一篇吧! {% endfor %} {% if posts.pages > 1 %} {% if posts.has_prev %} 上一页 {% endif %} {% for page_num in posts.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %} {% if page_num %} {{ page_num }} {% else %} {% endif %} {% endfor %} {% if posts.has_next %} 下一页 {% endif %} {% endif %}
关于博客

这是一个使用 Flask 构建的个人博客系统,支持用户注册、发布文章、评论等功能。

了解更多
分类统计
{% set categories = ['General', 'Technology', 'Life', 'Travel', 'Food'] %} {% for category in categories %} {{ category }} 0 {% endfor %} {% endblock %}

(注册页面)

{% extends "base.html" %}{% import "bootstrap/wtf.html" as wtf %}{% block title %}注册 - 我的博客{% endblock %}{% block content %}

用户注册

{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}

已有账号? 点击登录

{% endblock %}

继续创建其他模板文件...

首先安装Django:

pip install django django-crispy-forms pillow

然后创建Django项目:

django-admin startproject myblogcd myblogpython manage.py startapp blogpython manage.py startapp users

myblog/settings.py 配置:

import osfrom pathlib import PathBASE_DIR = Path(__file__).resolve.parent.parentSECRET_KEY = 'your-secret-key-here'DEBUG = TrueALLOWED_HOSTS = INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'crispy_forms', 'crispy_bootstrap5', 'blog', '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 = 'myblog.urls'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', ], }, },]WSGI_APPLICATION = 'myblog.wsgi.application'DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', }}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 = TrueUSE_TZ = TrueSTATIC_URL = '/static/'STATICFILES_DIRS = [BASE_DIR / 'static']MEDIA_URL = '/media/'MEDIA_ROOT = BASE_DIR / 'media'DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"CRISPY_TEMPLATE_PACK = "bootstrap5"LOGIN_REDIRECT_URL = 'blog:home'LOGIN_URL = 'users:login'LOGOUT_REDIRECT_URL = 'blog:home'

blog/models.py:

from django.db import modelsfrom django.contrib.auth.models import Userfrom django.urls import reversefrom django.utils import timezoneclass Category(models.Model): name = models.CharField(max_length=100, unique=True) description = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = '分类' verbose_name_plural = '分类' ordering = ['name'] def __str__(self): return self.nameclass Post(models.Model): STATUS_CHOICES = [ ('draft', '草稿'), ('published', '已发布'), ] title = models.CharField(max_length=200) slug = models.SlugField(max_length=200, unique_for_date='created_at') content = models.TextField excerpt = models.TextField(max_length=500, blank=True) author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts') category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts') status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') featured_image = models.ImageField(upload_to='blog_images/', blank=True, null=True) # 时间字段 created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) published_at = models.DateTimeField(null=True, blank=True) # 统计字段 view_count = models.PositiveIntegerField(default=0) like_count = models.PositiveIntegerField(default=0) class Meta: verbose_name = '文章' verbose_name_plural = '文章' ordering = ['-created_at'] indexes = [ models.Index(fields=['-created_at']), models.Index(fields=['status']), ] def __str__(self): return self.title def get_absolute_url(self): return reverse('blog:post_detail', args=[ self.created_at.year, self.created_at.month, self.created_at.day, self.slug ]) def save(self, *args, **kwargs): if self.status == 'published' and not self.published_at: self.published_at = timezone.now super.save(*args, **kwargs)class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_comments') content = models.TextField parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) active = models.BooleanField(default=True) class Meta: verbose_name = '评论' verbose_name_plural = '评论' ordering = ['created_at'] def __str__(self): return f'评论 by {self.author} on {self.post}'class Tag(models.Model): name = models.CharField(max_length=50, unique=True) posts = models.ManyToManyField(Post, related_name='tags', blank=True) class Meta: verbose_name = '标签' verbose_name_plural = '标签' ordering = ['name'] def __str__(self): return self.nameclass Like(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) post = models.ForeignKey(Post, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ['user', 'post'] verbose_name = '点赞' verbose_name_plural = '点赞' def __str__(self): return f'{self.user} 喜欢 {self.post}'

blog/forms.py:

from django import formsfrom .models import Post, Comment, Categoryclass PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'content', 'excerpt', 'category', 'status', 'featured_image'] widgets = { 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '输入文章标题'}), 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10, 'placeholder': '输入文章内容'}), 'excerpt': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '文章摘要(可选)'}), 'category': forms.Select(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-control'}), } def __init__(self, *args, **kwargs): super.__init__(*args, **kwargs) self.fields['category'].queryset = Category.objects.allclass CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['content'] widgets = { 'content': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': '写下你的评论...' }) }class SearchForm(forms.Form): query = forms.CharField( max_length=100, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': '搜索文章...' }) )

blog/views.py:

from django.shortcuts import render, get_object_or_404, redirectfrom django.contrib.auth.decorators import login_requiredfrom django.contrib import messagesfrom django.core.paginator import Paginatorfrom django.db.models import Q, Countfrom django.http import JsonResponsefrom django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteViewfrom django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixinfrom django.urls import reverse_lazyfrom .models import Post, Comment, Category, Tag, Likefrom .forms import PostForm, CommentForm, SearchFormclass PostListView(ListView): model = Post template_name = 'blog/home.html' context_object_name = 'posts' paginate_by = 6 def get_queryset(self): queryset = Post.objects.filter(status='published').select_related('author', 'category').prefetch_related('tags') # 分类过滤 category_slug = self.kwargs.get('category_slug') if category_slug: category = get_object_or_404(Category, name=category_slug) queryset = queryset.filter(category=category) # 搜索功能 query = self.request.GET.get('q') if query: queryset = queryset.filter( Q(title__icontains=query) | Q(content__icontains=query) | Q(excerpt__icontains=query) | Q(tags__name__icontains=query) ).distinct return queryset def get_context_data(self, **kwargs): context = super.get_context_data(**kwargs) context['categories'] = Category.objects.annotate(post_count=Count('posts')) context['recent_posts'] = Post.objects.filter(status='published')[:5] context['search_form'] = SearchForm(self.request.GET or None) return contextclass PostDetailView(DetailView): model = Post template_name = 'blog/post_detail.html' context_object_name = 'post' def get_queryset(self): return Post.objects.filter(status='published').select_related('author', 'category').prefetch_related('tags', 'comments__author') def get_context_data(self, **kwargs): context = super.get_context_data(**kwargs) context['comment_form'] = CommentForm context['comments'] = self.object.comments.filter(active=True, parent__isnull=True) # 增加阅读量 self.object.view_count += 1 self.object.save(update_fields=['view_count']) return contextclass PostCreateView(LoginRequiredMixin, CreateView): model = Post form_class = PostForm template_name = 'blog/post_form.html' def form_valid(self, form): form.instance.author = self.request.user messages.success(self.request, '文章创建成功!') return super.form_valid(form)class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model = Post form_class = PostForm template_name = 'blog/post_form.html' def form_valid(self, form): messages.success(self.request, '文章更新成功!') return super.form_valid(form) def test_func(self): post = self.get_object return self.request.user == post.authorclass PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): model = Post template_name = 'blog/post_confirm_delete.html' success_url = reverse_lazy('blog:home') def test_func(self): post = self.get_object return self.request.user == post.author def delete(self, request, *args, **kwargs): messages.success(request, '文章删除成功!') return super.delete(request, *args, **kwargs)@login_requireddef add_comment(request, pk): post = get_object_or_404(Post, pk=pk) if request.method == 'POST': form = CommentForm(request.POST) if form.is_valid: comment = form.save(commit=False) comment.post = post comment.author = request.user comment.save messages.success(request, '评论发布成功!') return redirect('blog:post_detail', pk=pk)@login_requireddef like_post(request, pk): post = get_object_or_404(Post, pk=pk) like, created = Like.objects.get_or_create(user=request.user, post=post) if not created: like.delete post.like_count -= 1 liked = False else: post.like_count += 1 liked = True post.save(update_fields=['like_count']) if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return JsonResponse({ 'liked': liked, 'like_count': post.like_count }) return redirect('blog:post_detail', pk=pk)def category_list(request): categories = Category.objects.annotate(post_count=Count('posts')) return render(request, 'blog/category_list.html', {'categories': categories})def about(request): return render(request, 'blog/about.html')

myblog/urls.py:

from django.contrib import adminfrom django.urls import path, includefrom django.conf import settingsfrom django.conf.urls.static import staticurlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls', namespace='blog')), path('users/', include('users.urls', namespace='users')),]if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

blog/urls.py:

from django.urls import pathfrom . import viewsapp_name = 'blog'urlpatterns = [ path('', views.PostListView.as_view, name='home'), path('about/', views.about, name='about'), path('categories/', views.category_list, name='category_list'), path('category//', views.PostListView.as_view, name='posts_by_category'), path('post/new/', views.PostCreateView.as_view, name='post_create'), path('post//', views.PostDetailView.as_view, name='post_detail'), path('post//update/', views.PostUpdateView.as_view, name='post_update'), path('post//delete/', views.PostDeleteView.as_view, name='post_delete'), path('post//comment/', views.add_comment, name='add_comment'), path('post//like/', views.like_post, name='like_post'),]

blog/admin.py:

from django.contrib import adminfrom .models import Category, Post, Comment, Tag, Like@admin.register(Category)class CategoryAdmin(admin.ModelAdmin): list_display = ['name', 'created_at', 'post_count'] search_fields = ['name'] prepopulated_fields = {'slug': ['name']} def post_count(self, obj): return obj.posts.count post_count.short_description = '文章数量'@admin.register(Post)class PostAdmin(admin.ModelAdmin): list_display = ['title', 'author', 'category', 'status', 'created_at', 'view_count', 'like_count'] list_filter = ['status', 'category', 'created_at', 'author'] search_fields = ['title', 'content'] prepopulated_fields = {'slug': ['title']} raw_id_fields = ['author'] date_hierarchy = 'created_at' ordering = ['-created_at'] fieldsets = ( ('基本信息', { 'fields': ('title', 'slug', 'author', 'category', 'status') }), ('内容', { 'fields': ('content', 'excerpt', 'featured_image') }), ('统计信息', { 'fields': ('view_count', 'like_count'), 'classes': ('collapse',) }), ('时间信息', { 'fields': ('created_at', 'updated_at', 'published_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at']@admin.register(Comment)class CommentAdmin(admin.ModelAdmin): list_display = ['author', 'post', 'created_at', 'active'] list_filter = ['active', 'created_at'] search_fields = ['author__username', 'content'] actions = ['approve_comments', 'disapprove_comments'] def approve_comments(self, request, queryset): queryset.update(active=True) approve_comments.short_description = "批准选中的评论" def disapprove_comments(self, request, queryset): queryset.update(active=False) disapprove_comments.short_description = "禁用选中的评论"@admin.register(Tag)class TagAdmin(admin.ModelAdmin): list_display = ['name', 'post_count'] search_fields = ['name'] def post_count(self, obj): return obj.posts.count post_count.short_description = '文章数量'@admin.register(Like)class LikeAdmin(admin.ModelAdmin): list_display = ['user', 'post', 'created_at'] list_filter = ['created_at'] search_fields = ['user__username', 'post__title']admin.site.site_header = '博客管理系统'admin.site.site_title = '博客管理'admin.site.index_title = '站点管理'

users/views.py:

from django.shortcuts import render, redirectfrom django.contrib.auth import login, logout, authenticatefrom django.contrib.auth.forms import UserCreationForm, AuthenticationFormfrom django.contrib import messagesfrom django.contrib.auth.decorators import login_requiredfrom django.views.generic import CreateView, UpdateViewfrom django.urls import reverse_lazyfrom .forms import UserRegisterForm, UserUpdateForm, ProfileUpdateFormclass RegisterView(CreateView): form_class = UserRegisterForm template_name = 'users/register.html' success_url = reverse_lazy('blog:home') def form_valid(self, form): response = super.form_valid(form) username = form.cleaned_data.get('username') messages.success(self.request, f'账号 {username} 创建成功!请登录。') return responsedef login_view(request): if request.method == 'POST': form = AuthenticationForm(request, data=request.POST) if form.is_valid: username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') user = authenticate(username=username, password=password) if user is not None: login(request, user) messages.success(request, f'欢迎回来,{username}!') next_page = request.GET.get('next') return redirect(next_page) if next_page else redirect('blog:home') else: messages.error(request, '用户名或密码错误') else: messages.error(request, '用户名或密码错误') form = AuthenticationForm return render(request, 'users/login.html', {'form': form})def logout_view(request): logout(request) messages.info(request, '您已成功退出登录') return redirect('blog:home')@login_requireddef profile(request): if request.method == 'POST': user_form = UserUpdateForm(request.POST, instance=request.user) profile_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile) if user_form.is_valid and profile_form.is_valid: user_form.save profile_form.save messages.success(request, '个人信息更新成功!') return redirect('users:profile') else: user_form = UserUpdateForm(instance=request.user) profile_form = ProfileUpdateForm(instance=request.user.profile) context = { 'user_form': user_form, 'profile_form': profile_form } return render(request, 'users/profile.html', context)

安装DRF:

pip install djangorestframework django-cors-headers

blog/api/serializers.py:

from rest_framework import serializersfrom ..models import Post, Comment, Category, Userclass UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email', 'date_joined']class CategorySerializer(serializers.ModelSerializer): post_count = serializers.ReadOnlyField class Meta: model = Category fields = ['id', 'name', 'description', 'post_count']class CommentSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = Comment fields = ['id', 'author', 'content', 'created_at']class PostListSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) category = CategorySerializer(read_only=True) comment_count = serializers.SerializerMethodField class Meta: model = Post fields = ['id', 'title', 'excerpt', 'author', 'category', 'created_at', 'view_count', 'like_count', 'comment_count'] def get_comment_count(self, obj): return obj.comments.countclass PostDetailSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) category = CategorySerializer(read_only=True) comments = CommentSerializer(many=True, read_only=True) class Meta: model = Post fields = ['id', 'title', 'content', 'author', 'category', 'created_at', 'updated_at', 'view_count', 'like_count', 'comments']class PostCreateSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ['title', 'content', 'excerpt', 'category', 'status'] def create(self, validated_data): validated_data['author'] = self.context['request'].user return super.create(validated_data)

blog/api/views.py:

from rest_framework import generics, permissions, statusfrom rest_framework.decorators import api_view, permission_classesfrom rest_framework.response import Responsefrom django.db.models import Qfrom ..models import Post, Comment, Categoryfrom .serializers import ( PostListSerializer, PostDetailSerializer, PostCreateSerializer, CommentSerializer, CategorySerializer)class PostListView(generics.ListAPIView): serializer_class = PostListSerializer permission_classes = [permissions.AllowAny] def get_queryset(self): queryset = Post.objects.filter(status='published').select_related('author', 'category') # 搜索功能 search_query = self.request.query_params.get('search', None) if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(content__icontains=search_query) ) # 分类过滤 category_id = self.request.query_params.get('category', None) if category_id: queryset = queryset.filter(category_id=category_id) return querysetclass PostDetailView(generics.RetrieveAPIView): queryset = Post.objects.filter(status='published') serializer_class = PostDetailSerializer permission_classes = [permissions.AllowAny]class PostCreateView(generics.CreateAPIView): queryset = Post.objects.all serializer_class = PostCreateSerializer permission_classes = [permissions.IsAuthenticated]class CategoryListView(generics.ListAPIView): queryset = Category.objects.all serializer_class = CategorySerializer permission_classes = [permissions.AllowAny]@api_view(['POST'])@permission_classes([permissions.IsAuthenticated])def add_comment(request, pk): try: post = Post.objects.get(pk=pk, status='published') except Post.DoesNotExist: return Response({'error': '文章不存在'}, status=status.HTTP_404_NOT_FOUND) serializer = CommentSerializer(data=request.data) if serializer.is_valid: serializer.save(author=request.user, post=post) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)@api_view(['POST'])@permission_classes([permissions.IsAuthenticated])def like_post(request, pk): try: post = Post.objects.get(pk=pk, status='published') except Post.DoesNotExist: return Response({'error': '文章不存在'}, status=status.HTTP_404_NOT_FOUND) liked = post.likes.filter(user=request.user).exists if liked: post.likes.filter(user=request.user).delete post.like_count -= 1 liked = False else: post.likes.create(user=request.user) post.like_count += 1 liked = True post.save(update_fields=['like_count']) return Response({ 'liked': liked, 'like_count': post.like_count })

myblog/settings_production.py:

from .settings import *import osDEBUG = FalseALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com']# 数据库配置DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ.get('DB_NAME'), 'USER': os.environ.get('DB_USER'), 'PASSWORD': os.environ.get('DB_PASSWORD'), 'HOST': os.environ.get('DB_HOST'), 'PORT': os.environ.get('DB_PORT', '5432'), }}# 静态文件配置STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'# 安全配置SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueSECURE_BROWSER_XSS_FILTER = TrueSECURE_CONTENT_TYPE_NOSNIFF = True# 日志配置LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'logs/django.log'), }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': True, }, },}

Dockerfile:

FROM python:3.11-slimWORKDIR /app# 安装系统依赖RUN apt-get update && apt-get install -y \ gcc \ postgresql-dev \ && rm -rf /var/lib/apt/lists/*# 复制依赖文件COPY requirements.txt .# 安装Python依赖RUN pip install --no-cache-dir -r requirements.txt# 复制项目文件COPY . .# 收集静态文件RUN python manage.py collectstatic --noinput# 创建日志目录RUN mkdir -p /app/logs# 设置环境变量ENV PYTHONUNBUFFERED=1# 暴露端口EXPOSE 8000# 启动命令CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myblog.wsgi:application"]

docker-compose.yml:

version: '3.8'services: web: build: . ports: - "8000:8000" environment: - DB_NAME=myblog - DB_USER=postgres - DB_PASSWORD=password - DB_HOST=db - SECRET_KEY=your-production-secret-key depends_on: - db volumes: - static_volume:/app/staticfiles - media_volume:/app/media db: image: postgres:13 volumes: - postgres_data:/var/lib/postgresql/data environment: - POSTGRES_DB=myblog - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - static_volume:/app/staticfiles - media_volume:/app/media - ./ssl:/etc/nginx/ssl depends_on: - webvolumes: postgres_data: static_volume: media_volume:今日重点学习内容

第30天:我们将进行项目实战,综合运用之前学过的所有知识,构建一个完整的全栈应用程序。

今天的内容非常丰富,建议先运行Flask示例,再逐步学习Django。有什么具体问题随时问我!

来源:琢磨先生起飞吧

相关推荐