)
Django实战从零构建书评网站Bookr的完整指南1. 环境准备与项目初始化在开始构建Bookr书评网站之前我们需要确保开发环境配置正确。Python 3.8是Django 3.x及更高版本的基础要求建议使用虚拟环境隔离项目依赖python -m venv bookr_env source bookr_env/bin/activate # Linux/macOS bookr_env\Scripts\activate # Windows安装必要的依赖包pip install django pillow # pillow用于图片处理创建Django项目和核心应用django-admin startproject bookr cd bookr python manage.py startapp reviews项目结构应如下所示bookr/ ├── bookr/ │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── reviews/ │ ├── migrations/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ ├── tests.py │ └── views.py └── manage.py在settings.py中配置基础设置INSTALLED_APPS [ ... reviews.apps.ReviewsConfig, ] # 添加模板目录配置 TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [os.path.join(BASE_DIR, templates)], ... } ] # 静态文件配置 STATIC_URL /static/ STATICFILES_DIRS [os.path.join(BASE_DIR, static)]2. 数据模型设计与实现书评网站的核心是数据模型我们需要设计书籍、作者、出版社和评论等实体。在reviews/models.py中定义模型from django.db import models from django.contrib.auth import get_user_model User get_user_model() class Publisher(models.Model): name models.CharField(max_length100) website models.URLField() email models.EmailField() def __str__(self): return self.name class Book(models.Model): title models.CharField(max_length100) publication_date models.DateField() isbn models.CharField(max_length20, uniqueTrue) publisher models.ForeignKey(Publisher, on_deletemodels.CASCADE) contributors models.ManyToManyField(Contributor, throughBookContributor) cover models.ImageField(upload_tobook_covers/, nullTrue, blankTrue) description models.TextField() def __str__(self): return self.title class Contributor(models.Model): first_names models.CharField(max_length100) last_names models.CharField(max_length100) email models.EmailField() def __str__(self): return f{self.first_names} {self.last_names} class BookContributor(models.Model): class ContributionRole(models.TextChoices): AUTHOR AUTHOR, Author CO_AUTHOR CO_AUTHOR, Co-Author EDITOR EDITOR, Editor book models.ForeignKey(Book, on_deletemodels.CASCADE) contributor models.ForeignKey(Contributor, on_deletemodels.CASCADE) role models.CharField(max_length20, choicesContributionRole.choices) class Review(models.Model): content models.TextField() rating models.IntegerField() date_created models.DateTimeField(auto_now_addTrue) date_edited models.DateTimeField(nullTrue, blankTrue) creator models.ForeignKey(User, on_deletemodels.CASCADE) book models.ForeignKey(Book, on_deletemodels.CASCADE, related_namereviews) def __str__(self): return f{self.creator.username}s review of {self.book.title}创建并应用迁移python manage.py makemigrations python manage.py migrate3. 视图与URL配置3.1 基础视图实现在reviews/views.py中创建基础视图from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import ListView, DetailView from .models import Book, Publisher, Review class BookListView(ListView): model Book template_name reviews/book_list.html context_object_name books paginate_by 10 def get_queryset(self): queryset super().get_queryset() search_term self.request.GET.get(search, ) if search_term: queryset queryset.filter(title__icontainssearch_term) return queryset class BookDetailView(DetailView): model Book template_name reviews/book_detail.html context_object_name book def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) context[reviews] self.object.reviews.all().order_by(-date_created) return context class PublisherDetailView(DetailView): model Publisher template_name reviews/publisher_detail.html context_object_name publisher def get_context_data(self, **kwargs): context super().get_context_data(**kwargs) context[books] self.object.book_set.all() return context3.2 URL路由配置在项目主urls.py中包含应用路由from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(reviews.urls)), ]在reviews/urls.py中定义应用路由from django.urls import path from .views import BookListView, BookDetailView, PublisherDetailView urlpatterns [ path(, BookListView.as_view(), namebook-list), path(books/int:pk/, BookDetailView.as_view(), namebook-detail), path(publishers/int:pk/, PublisherDetailView.as_view(), namepublisher-detail), ]4. 模板系统与前端实现4.1 基础模板结构创建templates/base.html作为基础模板!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}Bookr{% endblock %}/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet {% block extra_css %}{% endblock %} /head body nav classnavbar navbar-expand-lg navbar-dark bg-primary div classcontainer a classnavbar-brand href{% url book-list %}Bookr/a div classcollapse navbar-collapse form classd-flex ms-auto action{% url book-list %} methodget input classform-control me-2 typesearch namesearch placeholderSearch books... button classbtn btn-outline-light typesubmitSearch/button /form /div /div /nav div classcontainer mt-4 {% block content %}{% endblock %} /div script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js/script {% block extra_js %}{% endblock %} /body /html4.2 书籍列表模板创建templates/reviews/book_list.html{% extends base.html %} {% block title %}Book List{% endblock %} {% block content %} h1 classmb-4Book List/h1 div classrow {% for book in books %} div classcol-md-4 mb-4 div classcard h-100 {% if book.cover %} img src{{ book.cover.url }} classcard-img-top alt{{ book.title }} {% endif %} div classcard-body h5 classcard-title{{ book.title }}/h5 p classcard-text text-muted Published: {{ book.publication_date|date:Y }}br Publisher: {{ book.publisher.name }} /p a href{% url book-detail book.pk %} classbtn btn-primaryView Details/a /div /div /div {% empty %} div classcol-12 div classalert alert-infoNo books found./div /div {% endfor %} /div {% if is_paginated %} nav aria-labelPage navigation ul classpagination justify-content-center {% if page_obj.has_previous %} li classpage-item a classpage-link href?page{{ page_obj.previous_page_number }} aria-labelPrevious span aria-hiddentruelaquo;/span /a /li {% endif %} {% for num in page_obj.paginator.page_range %} {% if page_obj.number num %} li classpage-item activea classpage-link href#{{ num }}/a/li {% else %} li classpage-itema classpage-link href?page{{ num }}{{ num }}/a/li {% endif %} {% endfor %} {% if page_obj.has_next %} li classpage-item a classpage-link href?page{{ page_obj.next_page_number }} aria-labelNext span aria-hiddentrueraquo;/span /a /li {% endif %} /ul /nav {% endif %} {% endblock %}4.3 书籍详情模板创建templates/reviews/book_detail.html{% extends base.html %} {% block title %}{{ book.title }}{% endblock %} {% block content %} div classrow div classcol-md-4 {% if book.cover %} img src{{ book.cover.url }} classimg-fluid rounded alt{{ book.title }} {% endif %} /div div classcol-md-8 h1{{ book.title }}/h1 p classtext-muted Published: {{ book.publication_date|date:F j, Y }}br ISBN: {{ book.isbn }}br Publisher: a href{% url publisher-detail book.publisher.pk %}{{ book.publisher.name }}/a /p h3Contributors/h3 ul {% for contributor in book.bookcontributor_set.all %} li{{ contributor.contributor }} ({{ contributor.get_role_display }})/li {% endfor %} /ul h3Description/h3 p{{ book.description }}/p /div /div div classmt-5 h2Reviews/h2 {% if reviews %} {% for review in reviews %} div classcard mb-3 div classcard-body div classd-flex justify-content-between mb-2 h5 classcard-title{{ review.creator.username }}/h5 div {% for _ in 12345 %} {% if forloop.counter review.rating %} span classtext-warning★/span {% else %} span classtext-secondary★/span {% endif %} {% endfor %} /div /div p classcard-text{{ review.content }}/p p classtext-muted small Posted on {{ review.date_created|date:F j, Y }} {% if review.date_edited %} (edited on {{ review.date_edited|date:F j, Y }}) {% endif %} /p /div /div {% endfor %} {% else %} div classalert alert-infoNo reviews yet./div {% endif %} /div {% endblock %}5. 表单处理与用户交互5.1 创建评论表单在reviews/forms.py中创建表单from django import forms from .models import Review class ReviewForm(forms.ModelForm): class Meta: model Review fields [content, rating] widgets { content: forms.Textarea(attrs{rows: 4, class: form-control}), rating: forms.NumberInput(attrs{min: 1, max: 5, class: form-control}), } labels { content: Your Review, rating: Rating (1-5), }5.2 添加评论视图更新reviews/views.pyfrom django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic.edit import CreateView from django.urls import reverse from .forms import ReviewForm class ReviewCreateView(LoginRequiredMixin, CreateView): model Review form_class ReviewForm template_name reviews/review_form.html def form_valid(self, form): form.instance.creator self.request.user form.instance.book get_object_or_404(Book, pkself.kwargs[book_pk]) return super().form_valid(form) def get_success_url(self): return reverse(book-detail, kwargs{pk: self.kwargs[book_pk]})5.3 评论表单模板创建templates/reviews/review_form.html{% extends base.html %} {% block title %}Add Review{% endblock %} {% block content %} div classrow justify-content-center div classcol-md-8 div classcard div classcard-header h2Add Review for {{ book.title }}/h2 /div div classcard-body form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit classbtn btn-primarySubmit Review/button a href{% url book-detail book.pk %} classbtn btn-secondaryCancel/a /form /div /div /div /div {% endblock %}5.4 更新URL配置在reviews/urls.py中添加评论路由from .views import ReviewCreateView urlpatterns [ # ... existing patterns ... path(books/int:book_pk/reviews/new/, ReviewCreateView.as_view(), namereview-create), ]5.5 在书籍详情页添加评论链接更新book_detail.html{% if user.is_authenticated %} div classmt-3 a href{% url review-create book.pk %} classbtn btn-successAdd Review/a /div {% else %} div classalert alert-info mt-3 Please a href{% url login %}login/a to add a review. /div {% endif %}6. 管理后台配置6.1 注册模型到Admin在reviews/admin.py中配置管理界面from django.contrib import admin from .models import Publisher, Book, Contributor, BookContributor, Review class BookContributorInline(admin.TabularInline): model BookContributor extra 1 class BookAdmin(admin.ModelAdmin): list_display (title, publisher, publication_date) list_filter (publisher, publication_date) search_fields (title, isbn) filter_horizontal (contributors,) inlines [BookContributorInline] class ReviewAdmin(admin.ModelAdmin): list_display (book, creator, rating, date_created) list_filter (rating, date_created) search_fields (book__title, creator__username, content) admin.site.register(Publisher) admin.site.register(Book, BookAdmin) admin.site.register(Contributor) admin.site.register(Review, ReviewAdmin)6.2 创建超级用户运行以下命令创建管理员账户python manage.py createsuperuser7. 静态文件与媒体文件处理7.1 开发环境配置在settings.py中添加媒体文件配置MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)更新项目urls.py以支持开发环境下的媒体文件服务from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ... existing patterns ... ] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)7.2 静态文件组织创建静态文件目录结构static/ ├── css/ │ └── custom.css └── js/ └── custom.js在custom.css中添加自定义样式.book-cover { max-height: 400px; object-fit: contain; } .rating-stars { font-size: 1.2rem; }在基础模板中引入自定义静态文件{% load static %} link href{% static css/custom.css %} relstylesheet script src{% static js/custom.js %} defer/script8. 测试与部署准备8.1 编写基础测试在reviews/tests.py中添加测试from django.test import TestCase from django.urls import reverse from django.contrib.auth.models import User from .models import Publisher, Book class BookViewsTest(TestCase): classmethod def setUpTestData(cls): cls.publisher Publisher.objects.create( nameTest Publisher, websitehttps://example.com, emailpublisherexample.com ) cls.book Book.objects.create( titleTest Book, publication_date2023-01-01, isbn1234567890123, publishercls.publisher, descriptionTest description ) cls.user User.objects.create_user( usernametestuser, passwordtestpass123 ) def test_book_list_view(self): response self.client.get(reverse(book-list)) self.assertEqual(response.status_code, 200) self.assertContains(response, self.book.title) self.assertTemplateUsed(response, reviews/book_list.html) def test_book_detail_view(self): response self.client.get(reverse(book-detail, args[self.book.pk])) self.assertEqual(response.status_code, 200) self.assertContains(response, self.book.title) self.assertTemplateUsed(response, reviews/book_detail.html) def test_review_create_view_authenticated(self): self.client.login(usernametestuser, passwordtestpass123) response self.client.get(reverse(review-create, args[self.book.pk])) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, reviews/review_form.html) def test_review_create_view_unauthenticated(self): response self.client.get(reverse(review-create, args[self.book.pk])) self.assertEqual(response.status_code, 302) self.assertRedirects(response, f/accounts/login/?next/books/{self.book.pk}/reviews/new/)8.2 部署准备创建requirements.txt文件pip freeze requirements.txt配置生产环境设置可创建单独的settings_prod.pyfrom .settings import * DEBUG False ALLOWED_HOSTS [yourdomain.com, www.yourdomain.com] # 数据库配置 DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: bookr_prod, USER: bookr_user, PASSWORD: securepassword, HOST: localhost, PORT: 5432, } } # 静态文件配置 STATIC_ROOT os.path.join(BASE_DIR, staticfiles) STATICFILES_DIRS [os.path.join(BASE_DIR, static)] # 安全配置 SECURE_SSL_REDIRECT True SESSION_COOKIE_SECURE True CSRF_COOKIE_SECURE True SECURE_HSTS_SECONDS 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS True SECURE_HSTS_PRELOAD True9. 高级功能扩展9.1 添加API端点安装Django REST frameworkpip install djangorestframework在settings.py中添加应用INSTALLED_APPS [ ... rest_framework, ]创建API序列化器和视图# reviews/api/serializers.py from rest_framework import serializers from ..models import Book, Review class BookSerializer(serializers.ModelSerializer): class Meta: model Book fields [id, title, publication_date, isbn, publisher, description] class ReviewSerializer(serializers.ModelSerializer): creator serializers.StringRelatedField() class Meta: model Review fields [id, content, rating, date_created, creator] class BookDetailSerializer(BookSerializer): reviews ReviewSerializer(manyTrue, read_onlyTrue) class Meta(BookSerializer.Meta): fields BookSerializer.Meta.fields [reviews]# reviews/api/views.py from rest_framework import generics from ..models import Book from .serializers import BookSerializer, BookDetailSerializer class BookListAPIView(generics.ListAPIView): queryset Book.objects.all() serializer_class BookSerializer class BookDetailAPIView(generics.RetrieveAPIView): queryset Book.objects.all() serializer_class BookDetailSerializer配置API路由# reviews/api/urls.py from django.urls import path from .views import BookListAPIView, BookDetailAPIView urlpatterns [ path(books/, BookListAPIView.as_view(), nameapi-book-list), path(books/int:pk/, BookDetailAPIView.as_view(), nameapi-book-detail), ]在主urls.py中包含API路由urlpatterns [ ... path(api/, include(reviews.api.urls)), ]9.2 添加缓存支持在settings.py中配置缓存CACHES { default: { BACKEND: django.core.cache.backends.redis.RedisCache, LOCATION: redis://127.0.0.1:6379/1, } } # 视图缓存示例 from django.views.decorators.cache import cache_page cache_page(60 * 15) # 缓存15分钟 def my_view(request): ...9.3 性能优化使用django-debug-toolbar进行性能分析pip install django-debug-toolbar配置settings.pyINSTALLED_APPS [ ... debug_toolbar, ] MIDDLEWARE [ ... debug_toolbar.middleware.DebugToolbarMiddleware, ] INTERNAL_IPS [127.0.0.1]配置urls.pyif settings.DEBUG: import debug_toolbar urlpatterns [ path(__debug__/, include(debug_toolbar.urls)), ] urlpatterns10. 项目总结与后续优化方向经过以上步骤我们已经完成了Bookr书评网站的基础功能开发。在实际项目中还可以考虑以下优化方向用户认证增强实现电子邮件验证、密码重置、社交账号登录等功能搜索功能扩展集成Elasticsearch或Whoosh实现全文搜索推荐系统基于用户行为实现书籍推荐通知系统当书籍有新评论时通知关注者国际化支持添加多语言支持前端优化使用Vue.js或React实现更动态的交互体验性能监控集成Sentry错误监控和性能分析工具# 示例使用Django信号实现新评论通知 from django.db.models.signals import post_save from django.dispatch import receiver from django.core.mail import send_mail from .models import Review receiver(post_save, senderReview) def notify_book_followers(sender, instance, created, **kwargs): if created: subject fNew review for {instance.book.title} message f{instance.creator.username} posted a new review:\n\n{instance.content} recipients [user.email for user in instance.book.followers.all()] send_mail(subject, message, noreplybookr.com, recipients)