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

Django项目登录分配最新条款及解决非空约束错误

Django用户条款分配问题:NOT NULL约束错误修复

问题背景

在Django项目中,需要实现用户登录时自动分配最新的服务条款(Terms and Conditions),若用户未同意则展示条款。但创建UserAgreement对象时触发NOT NULL constraint failed: tac_useragreement.terms_and_conditions_id错误,修改视图后仍未解决。

条款模型

class TermsAndConditions(models.Model):
    text = models.TextField()
    date_modified = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'Terms and conditions, last modified on {self.date_modified}'

class UserAgreement(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True)
    agreed = models.BooleanField(default=False)
    agreed_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
    terms_and_conditions = models.ForeignKey(TermsAndConditions, on_delete=models.PROTECT)

    def __str__(self):
        return f'Agreement for user {self.user.username}'

原视图代码

@login_required
def show_terms_and_conditions(request):
    try:
        user_agreement = UserAgreement.objects.get(user=request.user)
    except UserAgreement.DoesNotExist:
        user_agreement = UserAgreement.objects.create(user=request.user)
    terms_and_conditions = user_agreement.terms_and_conditions

    return render(request, 'tac/termsandconditions.html', {'terms_and_conditions': terms_and_conditions})

@login_required
def agree(request):
    if request.method == "POST":
        user_agreement = UserAgreement.objects.get(user=request.user)
        user_agreement.agreed = True
        user_agreement.save()
        return redirect(reverse('........'))
    else:
        return redirect(reverse('.......'))

尝试过的修改(未生效)

# @login_required
# def show_terms_and_conditions(request):
#     latest_tac = TermsAndConditions.objects.latest('date_modified')
#     user_agreement, created = UserAgreement.objects.get_or_create(user=request.user, terms_and_conditions=latest_tac)
#     if created:
#         # If the user agreement was created in this request, set `agreed` to False and update the `agreed_at` field
#         user_agreement.agreed = False
#         user_agreement.agreed_at = timezone.now()
#         user_agreement.save()
#     terms_and_conditions = user_agreement.terms_and_conditions
# 
#     return render(request, 'tac/termsandconditions.html', {'terms_and_conditions': terms_and_conditions, 'user_agreement': user_agreement})

报错堆栈信息

sqlite3.IntegrityError: NOT NULL constraint failed: tac_useragreement.terms_and_conditions_id

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\views\generic\base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\views\generic\base.py", line 142, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\User\Desktop\Project\my_gym\views.py", line 61, in get
    user_agreement = UserAgreement.objects.create(user=request.user)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\query.py", line 671, in create
    obj.save(force_insert=True, using=self.db)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\base.py", line 812, in save
    self.save_base(
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\base.py", line 863, in save_base
    updated = self._save_table(
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\base.py", line 1006, in _save_table
    results = self._do_insert(
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\base.py", line 1047, in _do_insert
    return manager._insert(
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\query.py", line 1790, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\models\sql\compiler.py", line 1660, in execute_sql
    cursor.execute(sql, params)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\utils.py", line 103, in execute
    return super().execute(sql, params)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\User\Desktop\Project\venv\lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: NOT NULL constraint failed: tac_useragreement.terms_and_conditions_id
[10/Feb/2023 20:41:47] "GET /favicon.ico/ HTTP/1.1" 500 177365

问题原因与修复方案

原因分析

错误核心是:UserAgreement模型中terms_and_conditions是必填外键(未设置null=True或blank=True),但原视图创建UserAgreement时未传入该字段值,违反了数据库非空约束。

尝试的修改逻辑存在漏洞:get_or_create的参数包含terms_and_conditions=latest_tac,会导致用户每次访问都绑定最新条款,无法保留用户已同意的旧版本记录。正确逻辑应为:用户首次登录绑定最新条款;若条款更新,用户需重新同意新版本。

最终修复视图

from django.utils import timezone

@login_required
def show_terms_and_conditions(request):
    # 获取最新的服务条款,若不存在则初始化默认条款
    try:
        latest_tac = TermsAndConditions.objects.latest('date_modified')
    except TermsAndConditions.DoesNotExist:
        latest_tac = TermsAndConditions.objects.create(text="默认服务条款内容")

    # 获取用户当前的协议记录,首次创建时绑定最新条款并设为未同意
    user_agreement, created = UserAgreement.objects.get_or_create(
        user=request.user,
        defaults={
            'terms_and_conditions': latest_tac,
            'agreed': False,
            'agreed_at': timezone.now()
        }
    )

    # 检查用户当前协议是否为最新版本,若不是则创建新的未同意协议
    if user_agreement.terms_and_conditions != latest_tac:
        user_agreement = UserAgreement.objects.create(
            user=request.user,
            terms_and_conditions=latest_tac,
            agreed=False,
            agreed_at=timezone.now()
        )

    return render(request, 'tac/termsandconditions.html', {
        'terms_and_conditions': user_agreement.terms_and_conditions,
        'user_agreement': user_agreement
    })

@login_required
def agree(request):
    if request.method == "POST":
        # 获取用户最新的未同意协议
        user_agreement = UserAgreement.objects.filter(
            user=request.user,
            agreed=False
        ).latest('agreed_at')
        user_agreement.agreed = True
        user_agreement.agreed_at = timezone.now()
        user_agreement.save()
        return redirect(reverse('你的目标视图名称'))
    else:
        return redirect(reverse('show_terms_and_conditions'))

额外优化建议

  • 模型版本化:为UserAgreement添加version字段,方便区分不同版本的条款协议:
class UserAgreement(models.Model):
    # ... 原有字段 ...
    version = models.CharField(max_length=20, default="1.0")  # 手动或自动生成版本号
  • 登录拦截:在用户登录成功后自动检查未同意条款,直接跳转至条款页面:
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

@receiver(user_logged_in)
def check_terms_agreement(sender, request, user, **kwargs):
    try:
        latest_tac = TermsAndConditions.objects.latest('date_modified')
        has_agreed_latest = UserAgreement.objects.filter(
            user=user,
            terms_and_conditions=latest_tac,
            agreed=True
        ).exists()
        if not has_agreed_latest:
            request.session['next_url'] = request.path
            return redirect('show_terms_and_conditions')
    except TermsAndConditions.DoesNotExist:
        pass

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:45:38