Django实现模型数据传视图及Survey提交发放10积分
问题说明
需要实现两个核心功能:
- 将
models.py中定义的问卷相关模型数据传递到视图层,最终渲染展示给用户 - 用户每成功提交1份
Survey问卷,自动为当前操作用户增加10个账户积分
现有代码存在两处核心缺失:一是模型关联不全、关联查询缺失,模板拿不到完整问卷数据;二是Participant模型未记录提交用户、提交逻辑未做登录校验也未调用已写好的积分发放方法。
具体修改步骤
第一步:修改models.py 补全模型关联
原有Participant模型只关联了问卷,没有记录提交用户,既无法精准给对应账户发积分,也无法防范用户重复提交刷积分。
修改后的Participant模型代码:
class Participant(models.Model): # 新增外键关联用户模型,配置反向查询名方便后续查用户的参与记录 user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="survey_participations") survey = models.ForeignKey(Survey, on_delete=models.CASCADE) participation_datetime = models.DateTimeField('Participation DateTime') class Meta: # 增加联合唯一约束,同一个用户同一份问卷仅能提交一次 unique_together = ('user', 'survey') def __str__(self): return f"Participant {self.user.username} {self.participation_datetime}"
注意:修改完模型后需要依次执行python manage.py makemigrations、python manage.py migrate命令同步数据库结构。
第二步:修改视图层逻辑,补全数据传递+积分发放能力
2.1 优化问卷详情标签逻辑,传递完整渲染数据
原有survey_details方法仅查询了Survey主表数据,没有关联加载对应的问题、选项,前端无法渲染完整问卷,同时缺少异常处理,问卷ID不存在时会直接抛500错误。
修改后的代码:
from django.shortcuts import get_object_or_404 from django.db.models import Prefetch @register.inclusion_tag('survey/survey_details.html', takes_context=True) def survey_details(context, survey_id): # 用get_object_or_404处理不存在的问卷ID,返回404而非服务错误 # 预加载关联的问题、问题下的选项,减少重复数据库查询 survey = get_object_or_404( Survey.objects.prefetch_related( Prefetch('question_set', queryset=Question.objects.prefetch_related('choice_set')) ), id=survey_id ) request = context['request'] # 额外传递当前用户、用户是否已参与的标记,方便模板做状态判断 return { 'survey': survey, 'user': request.user, 'has_participated': survey.participant_set.filter(user=request.user).exists() if request.user.is_authenticated else False }
2.2 重构问卷提交逻辑,增加校验+积分发放
原有提交逻辑没有登录校验、没有防重复提交、没有记录提交人、没有触发积分发放,修改后代码:
from django.contrib.auth.decorators import login_required from django.http import HttpResponseForbidden from django.db.models import F # 加登录校验装饰器,未登录用户不允许提交问卷 @login_required @require_http_methods(["POST"]) def submit_survey(request): form_data = request.POST.copy() form_items = list(form_data.items()) form_items.pop(0) # 移除列表首位的csrf token survey = None choice_list = [] # 先校验所有提交选项的合法性,避免中途报错导致数据不一致 for item in form_items: choice_str, choice_id = item choice_id = int(choice_id.split('/')[1]) choice = Choice.objects.select_related('question__survey').get(id=choice_id) if survey is None: survey = choice.question.survey choice_list.append(choice) if survey is not None: # 重复提交校验,已参与过的用户直接返回禁止提示 if Participant.objects.filter(user=request.user, survey=survey).exists(): return HttpResponseForbidden("你已经提交过这份问卷,无法重复提交") # 批量更新选项票数 for choice in choice_list: choice.votes = F('votes') + 1 choice.save(update_fields=('votes',)) # 写入参与记录 Participant.objects.create( user=request.user, survey=survey, participation_datetime=timezone.now() ) # 直接调用User模型已实现的give_coins方法,给当前用户加10积分 request.user.give_coins(count=10) return redirect('/submit_success/')
第三步:模板渲染适配
在survey/survey_details.html模板中,可以直接遍历传入的问卷数据渲染页面,参考代码:
<div class="survey-container"> <h2>{{ survey.name }}</h2> <p>发布时间:{{ survey.published_on|date:"Y-m-d H:i" }}</p> {% if has_participated %} <p>你已完成这份问卷,累计获得10积分</p> {% else %} <form method="POST" action="/submit_survey/"> {% csrf_token %} {% for question in survey.question_set.all %} <div class="question-item"> <p>{{ forloop.counter }}. {{ question.question_text }}</p> {% for choice in question.choice_set.all %} <label style="display:block;margin:8px 0;"> <input type="radio" name="choice/{{ choice.id }}" value="{{ choice.id }}" required> {{ choice.choice_text }} </label> {% endfor %} </div> {% endfor %} <button type="submit" style="margin-top:16px;">提交问卷</button> </form> {% endif %} <p style="margin-top:20px;">当前账户总积分:{{ user.coins }}</p> </div>
注意事项
- 所有涉及用户资产变动的接口必须加登录校验,否则匿名用户提交无法关联账户发积分
- 用户+问卷的联合唯一约束必须配置,否则用户可以通过重复提交刷取大量积分
- 关联数据预加载可以有效减少数据库查询次数,明显提升页面加载速度
内容的提问来源于stack exchange,提问作者devo
相关产品推荐
相关产品推荐

