You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Django模型多字段实现用户输入验证及权限访问控制

Django密钥+手机号验证访问功能修正方案

需求描述

我是编程新手,今年8月开始自学Python和Django,现在要实现一个功能:已认证的Django用户输入与数据库匹配的密钥(key)和手机号后,才能访问指定URL资源。流程是:首页按钮跳转对应视图,展示包含两个输入字段的表单;用户提交数据后验证,匹配成功就跳转到目标URL。以下是我的实现代码,求帮忙解决问题:


原代码

Models.py

from django.db import models
from .utils import generate_code

class Secret(models.Model):
    name = models.CharField(max_length=50)
    key = models.CharField(max_length=15, blank=True, unique=True)
    phone_number = models.CharField(max_length=13)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return self.name 

    def save(self, *args, **kwargs):
        if self.key == "":
            self.key = generate_code()
        return super().save(*args, **kwargs)   

Utils.py

import uuid

def generate_code():
    code = uuid.uuid4()
    code_mod = str(code).replace('-', '').upper()[:15]
    return code_mod

Forms.py

from django import forms
from .models import Secret

class SecretModelForm(forms.ModelForm):
    class Meta:
        model = Secret
        fields = ["phone_number", "key"]
        widgets = {"key": forms.TextInput(attrs={"placeholder": "Inserisci la chiave segreta.."})}

Views.py

from django.shortcuts import redirect, render
from .forms import SecretModelForm
from .models import Secret

def homepage(request):
    context = {}
    return render(request, 'core/homepage.html', context)

def access_to_events_by_secret(request):
    keys = Secret.objects.order_by('key')
    phone_numbers = Secret.objects.order_by('phone_number')
    form = SecretModelForm()
    context = {"form": form, "keys": keys, "phone_numbers": phone_numbers}
    if request.method == "POST":
        instance_k = Secret.objects.get(key=request.POST['key'])
        instance_p_n = Secret.objects.get(phone_number=request.POST['phone_number'])
        form = SecretModelForm(request.POST, instance_k=instance_k, instance_p_n=instance_p_n)
        if form.is_valid():
            if request.POST.get("key").strip() == instance_k.key and request.POST.get("phone_number").strip() == instance_p_n.phone_number:
                return redirect("/events/")

    return render(request, "core/secret_access.html", context)    

Secret_access.html

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block head_title %}{{ block.super }} -  Secret{% endblock head_title %}
{% block content %}
    <div class="container">
            <h1 class="text-light text-center mt-5">Secret Access</h1>
           
            <div class="row mt-2 justify-content-center">
                <div class="col-lg-5 col-sm-auto d-flex justify-content-center">
                     <form method="POST" novalidate>
                        {% csrf_token %}
                        {{ form.key }} 
                        {{ form.phone_number }}
                        <input type="hidden" name="key" value="{{ key }}" />
                        <input type="hidden" name="phone_number" value="{{ phone_number }}" />
                        <div class="container text-center">
                          <input type="submit" class="btn btn-sm" value="Accedi!">
                        </div>
                      </form>
                 </div>
            </div>
    </div>
{% endblock content %}

问题分析与修正代码

核心问题点

  1. 视图层异常风险:使用Secret.objects.get()会在数据不存在时抛出DoesNotExist异常,导致页面崩溃;且分开查询key和手机号无法确保属于同一个Secret记录。
  2. 表单使用错误:这里不需要用ModelForm(用于创建/更新模型实例),只需普通表单验证输入;ModelForm不支持传入多个instance参数。
  3. 模板冗余干扰:隐藏input字段无意义,且未传递对应变量,会覆盖用户输入的有效数据。
  4. 未做登录验证:没有限制只有已认证用户能访问验证页面。
  5. 无错误反馈:验证失败时未提示用户错误信息。

修正后的代码

1. Forms.py(改用普通表单)

from django import forms

class SecretAccessForm(forms.Form):
    key = forms.CharField(
        max_length=15,
        widget=forms.TextInput(attrs={"placeholder": "输入密钥..", "class": "form-control mb-3"})
    )
    phone_number = forms.CharField(
        max_length=13,
        widget=forms.TextInput(attrs={"placeholder": "输入手机号..", "class": "form-control"})
    )

2. Views.py(修复验证逻辑+登录验证)

from django.shortcuts import redirect, render
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import SecretAccessForm
from .models import Secret

def homepage(request):
    context = {}
    return render(request, 'core/homepage.html', context)

@login_required  # 强制用户必须登录才能访问此视图
def access_to_events_by_secret(request):
    form = SecretAccessForm()
    if request.method == "POST":
        form = SecretAccessForm(request.POST)
        if form.is_valid():
            # 从表单获取清洗后的数据
            input_key = form.cleaned_data['key'].strip()
            input_phone = form.cleaned_data['phone_number'].strip()
            
            # 查询同时匹配key和手机号的Secret记录
            try:
                Secret.objects.get(key=input_key, phone_number=input_phone)
                # 匹配成功,跳转目标页面
                return redirect("/events/")
            except Secret.DoesNotExist:
                # 匹配失败,给用户错误提示
                messages.error(request, "密钥或手机号错误,请重新输入")
    
    context = {"form": form}
    return render(request, "core/secret_access.html", context)

3. Secret_access.html(去掉冗余字段+添加错误提示)

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block head_title %}{{ block.super }} - 密钥验证{% endblock head_title %}
{% block content %}
    <div class="container">
        <h1 class="text-light text-center mt-5">密钥验证</h1>
        
        <!-- 显示错误提示 -->
        {% if messages %}
            {% for message in messages %}
                <div class="alert alert-danger text-center mt-3">{{ message }}</div>
            {% endfor %}
        {% endif %}
        
        <div class="row mt-2 justify-content-center">
            <div class="col-lg-5 col-sm-auto d-flex justify-content-center">
                <form method="POST" novalidate>
                    {% csrf_token %}
                    {{ form.as_p }}  <!-- 简化表单渲染,自动应用crispy样式 -->
                    <div class="container text-center">
                        <input type="submit" class="btn btn-primary btn-sm" value="登录">
                    </div>
                </form>
            </div>
        </div>
    </div>
{% endblock content %}

4. Models.py和Utils.py(无需修改,保持原逻辑即可)


额外说明

  • 确保已在项目中启用django.contrib.messages应用(默认已启用),用于错误提示。
  • @login_required装饰器会将未登录用户跳转到登录页面,需在settings.py中配置LOGIN_URL(默认是/accounts/login/)。
  • 表单渲染使用{{ form.as_p }}配合crispy_forms,会自动生成带样式的输入框,比手动渲染字段更简洁。

内容的提问来源于stack exchange,提问作者Giovanni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:12:01