Django个人任务板完整实践 · Django任务板完整工程包含哪些文件至全部工程文件与同源正文
适用:Python 3.12、Django 5.2.18(5.2 LTS)。先掌握函数、模块、虚拟环境和 HTTP 的请求/响应。示例未执行;本轮不安装依赖、不启动服务、不运行框架或迁移。
Django个人任务板完整实践
适用:Python 3.12、Django 5.2.18(5.2 LTS)。先掌握函数、模块、虚拟环境和 HTTP 的请求/响应。示例未执行;本轮不安装依赖、不启动服务、不运行框架或迁移。
Django任务板完整工程包含哪些文件
完整工程固定依赖 Django 5.2.18,目标解释器 Python 3.12,包含配置、URL、模型、表单、视图、后台、模板、手写迁移和测试规格。数据保存在本地 SQLite;不依赖外部数据库服务。环境变量直接从进程读取,.env.example 是输入说明,不会自动加载。
成功标准是已登录用户完成新增、筛选、编辑和删除,并且不能读取或修改别人的任务。新增和编辑共享表单,但不新增可编辑 owner。源码与下方所有 include 同源,README 包含未来自行安装和启动的三平台命令;交付时示例未执行。
Django绑定表单怎样保留错误输入
GET 创建未绑定表单,POST 把 request.POST 交给绑定表单。不能写 request.POST or None,因为空 POST 也必须被视为一次提交并报告缺失字段。只有 POST 且 is_valid 为真才写数据库,错误时原表单连同字段错误重新渲染,输入得以保留。
成功保存后重定向列表,避免刷新浏览器重复提交同一 POST;这叫 POST/Redirect/GET。浏览器仍可能重发请求,因此它不取代数据库唯一约束或业务幂等策略。列表默认显示全部,state=open/done 才附加布尔条件,未知 state 退回全部,不拼接原始 SQL。
Django任务表单怎样携带CSRF令牌
每个修改表单包含 csrf_token,渲染时产生供浏览器提交的隐藏令牌,CsrfViewMiddleware 在不安全方法中检查令牌及相关来源条件。Cookie 保存 CSRF secret 的相关值,提交令牌用于证明请求与当前站点页面的关系;登录身份本身不能代替它。
本例使用同源普通 HTML 表单,不配置跨域放行,不关闭 CSRF 中间件。登录、新增、编辑、删除和退出均覆盖 token。GET 不做删除,因此链接预取、爬虫和浏览器缓存不会误删任务。未来前后端分离时需另外分析 Origin、Cookie 属性和凭证策略,不能把本例表单步骤直接等同跨域配置。
Django任务板需要哪些隔离测试
测试客户端覆盖正常 CRUD 与完成状态筛选,也覆盖匿名跳转、越权 404、GET 删除 405、空POST、空白与超长标题、新增/编辑重复冲突、保留原标题、跨用户同名及伪造 owner。普通 TestCase 客户端默认放松 CSRF 检查,因此另建 Client(enforce_csrf_checks=True) 验证无 token 得到 403、有 token 成功创建。
测试不能只断言状态码,还要核对任务数量、owner、修改后的字段、删除结果及列表隔离。重复保存后再发 GET,确认异常恢复路径没有把后续数据库查询留在坏事务里。当前只检查测试源码和场景完整性,没有执行测试客户端,没有创建测试数据库。
全部工程文件与同源正文
下列代码读取 examples/python/p2-django 原文件,不维护第二份副本。迁移为手写源文件,测试为未执行规格。
requirements.txt
Django==5.2.18.env.example
# Settings reads process environment, not this file automatically.
DJANGO_SECRET_KEY=replace-with-a-long-local-random-value
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,testserver.gitignore
.venv/
__pycache__/
*.pyc
db.sqlite3
.envmanage.py
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taskboard.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)taskboard/init.py
# Project configuration package.taskboard/settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
INSTALLED_APPS = [
"django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles",
"tasks.apps.TasksConfig",
]
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 = "taskboard.urls"
TEMPLATES = [{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"], "APP_DIRS": True,
"OPTIONS": {"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]},
}]
WSGI_APPLICATION = "taskboard.wsgi.application"
ASGI_APPLICATION = "taskboard.asgi.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 = True
USE_TZ = True
STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "tasks:list"
LOGOUT_REDIRECT_URL = "login"taskboard/urls.py
from django.contrib import admin
from django.urls import include, path
from django.contrib.auth.views import LoginView, LogoutView
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/login/", LoginView.as_view(), name="login"),
path("accounts/logout/", LogoutView.as_view(), name="logout"),
path("", include("tasks.urls")),
]taskboard/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taskboard.settings")
application = get_wsgi_application()taskboard/asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taskboard.settings")
application = get_asgi_application()tasks/init.py
# Taskboard application.tasks/apps.py
from django.apps import AppConfig
class TasksConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tasks"tasks/models.py
from django.conf import settings
from django.db import models
class Task(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=120)
done = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at", "-pk"]
constraints = [models.UniqueConstraint(fields=["owner", "title"], name="task_owner_title_unique")]
def __str__(self):
return self.titletasks/forms.py
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ["title", "done"]
def clean_title(self):
title = self.cleaned_data["title"].strip()
if not title:
raise forms.ValidationError("标题不能为空。")
return titletasks/views.py
from django.contrib.auth.decorators import login_required
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_http_methods, require_POST
from .forms import TaskForm
from .models import Task
@login_required
@require_http_methods(["GET"])
def task_list(request):
tasks = Task.objects.filter(owner=request.user)
state = request.GET.get("state", "all")
if state in {"open", "done"}:
tasks = tasks.filter(done=state == "done")
return render(request, "tasks/list.html", {"tasks": tasks, "state": state})
@login_required
@require_http_methods(["GET", "POST"])
def task_edit(request, pk=None):
task = get_object_or_404(Task, pk=pk, owner=request.user) if pk is not None else None
form = TaskForm(request.POST if request.method == "POST" else None, instance=task)
if request.method == "POST" and form.is_valid():
saved = form.save(commit=False)
saved.owner = request.user
try:
with transaction.atomic():
saved.save()
except IntegrityError:
# This model has one business constraint; transaction has rolled back.
form.add_error(None, "保存冲突,请检查是否已有同名任务,再重试。")
else:
return redirect("tasks:list")
return render(request, "tasks/form.html", {"form": form, "task": task})
@login_required
@require_POST
def task_delete(request, pk):
task = get_object_or_404(Task, pk=pk, owner=request.user)
task.delete()
return redirect("tasks:list")tasks/urls.py
from django.urls import path
from . import views
app_name = "tasks"
urlpatterns = [
path("", views.task_list, name="list"),
path("tasks/new/", views.task_edit, name="create"),
path("tasks/<int:pk>/edit/", views.task_edit, name="edit"),
path("tasks/<int:pk>/delete/", views.task_delete, name="delete"),
]继续阅读
- 知识专题
- 排错
- 排错