Django中如何用类视图实现点击按钮切换BooleanField值
如何用Django类视图实现Task状态一键切换
我正在做一个TodoList练习项目,models.py代码如下:
class Tag(models.Model): name = models.CharField(max_length=60) def __str__(self): return self.name class Task(models.Model): TASK_STATUS_CHOICES = [(False, 'Not done'), (True, 'Done')] content = models.CharField(max_length=250) created = models.DateTimeField(auto_now_add=True) deadline = models.DateTimeField( default=timezone.now, null=True, blank=True, help_text="Enter date and time in '2023-05-13 06:31' format" ) is_completed = models.BooleanField( default=False, choices=TASK_STATUS_CHOICES ) tags = models.ManyToManyField(Tag, related_name="tasks") class Meta: ordering = ["is_completed", "-created"] def __str__(self): return self.content
此前我用函数视图实现了点击按钮切换is_completed字段值的功能,代码可正常运行:
原函数视图实现
views.py
def task_complete_or_undo(request, pk): task = Task.objects.get(id=pk) task.is_completed = not task.is_completed task.save() return redirect("/")
urls.py
path( "task/<int:pk>/task-status", task_complete_or_undo, name="task-status" ),
index.html
{% if task.is_completed %} <span style="color: green">{{ task.get_is_completed_display }}</span> <a href="{% url 'todo_list_app:task-status' pk=task.id %}" class="btn btn-secondary btn-sm link-to-page float-right"> Undo </a> {% else %} <span style="color: red">{{ task.get_is_completed_display }}</span> <a href="{% url 'todo_list_app:task-status' pk=task.id %}" class="btn btn-success btn-sm link-to-page float-right"> Complete </a> {% endif %}
现在需要改用类视图重写,但尝试两种方式都遇到问题:
- 自定义
View类时,仅实现post方法,点击按钮返回“Method Not Allowed”错误(此外代码里还写错了字段名,把is_completed写成了is_done):
class TaskStatusUpdateView(View): @staticmethod def post(request, pk): task = Task.objects.get(id=pk) task.is_done = not task.is_done task.save() return redirect("todo_list_app:index")
- 改用
UpdateView时,点击按钮会跳转到更新表单,需要手动提交,不符合一键切换的需求(同样存在字段名错误):
class TaskStatusUpdateView(generic.UpdateView): model = Task fields = ["is_completed"] @staticmethod def update(request, pk): task = Task.objects.get(id=pk) task.is_done = not task.is_done task.save() return redirect("todo_list_app:index")
解决方案
方案一:自定义View类(支持GET/POST请求)
方式1:兼容原GET请求(快速适配)
修正字段名错误,实现get方法匹配前端a标签的GET请求:
from django.views import View from django.shortcuts import get_object_or_404, redirect from .models import Task class TaskStatusUpdateView(View): def get(self, request, pk): # 用get_object_or_404替代get,避免任务不存在时抛出异常 task = get_object_or_404(Task, id=pk) task.is_completed = not task.is_completed task.save() return redirect("todo_list_app:index")
路由配置只需把视图换成类视图的as_view()方法:
path( "task/<int:pk>/task-status", TaskStatusUpdateView.as_view(), name="task-status" ),
前端代码无需修改,沿用原a标签即可。
方式2:遵循HTTP规范用POST请求(推荐)
根据HTTP规范,修改数据的操作应使用POST请求,避免误触或爬虫触发。调整前端为表单提交:
{% if task.is_completed %} <span style="color: green">{{ task.get_is_completed_display }}</span> <form action="{% url 'todo_list_app:task-status' pk=task.id %}" method="POST" class="float-right"> {% csrf_token %} <button type="submit" class="btn btn-secondary btn-sm">Undo</button> </form> {% else %} <span style="color: red">{{ task.get_is_completed_display }}</span> <form action="{% url 'todo_list_app:task-status' pk=task.id %}" method="POST" class="float-right"> {% csrf_token %} <button type="submit" class="btn btn-success btn-sm">Complete</button> </form> {% endif %}
对应的视图类改为处理POST请求:
class TaskStatusUpdateView(View): def post(self, request, pk): task = get_object_or_404(Task, id=pk) task.is_completed = not task.is_completed task.save() return redirect("todo_list_app:index")
方案二:用UpdateView实现
UpdateView默认会渲染表单,要实现一键切换,需重写方法跳过表单渲染逻辑:
from django.views.generic.edit import UpdateView from .models import Task from django.urls import reverse_lazy class TaskStatusUpdateView(UpdateView): model = Task fields = ['is_completed'] success_url = reverse_lazy("todo_list_app:index") # 处理POST请求(配合前端表单) def form_valid(self, form): # 直接切换状态,忽略表单提交的值 self.object.is_completed = not self.object.is_completed self.object.save() return super().form_valid(form) # 若要支持GET请求(配合原a标签),重写get方法 def get(self, request, *args, **kwargs): self.object = self.get_object() self.object.is_completed = not self.object.is_completed self.object.save() return redirect(self.success_url)
路由配置同样使用as_view()方法即可。
内容的提问来源于stack exchange,提问作者Maksym Maksimov
相关产品推荐
相关产品推荐

