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
相关产品推荐
相关产品推荐

