Django中create_submission_form路由反向解析失败求助
Django NoReverseMatch 错误排查与解决
错误详情
NoReverseMatch at /dashboard/ Reverse for 'create_submission_form' with keyword arguments '{'docstash_key': ''}' not found. 1 pattern(s) tried: ['core/submission/new/(?P<docstash_key>[^/]+)/\\Z']
触发错误的HTML代码
<a class="dropdown-item" href="{% url 'core:create_submission_form' docstash_key=docstash.key %}">
URL配置
path('new/<str:docstash_key>/', views.create_submission_form, name='create_submission_form'),
对应视图函数
@with_docstash() def create_submission_form(request): if request.method == 'POST' and 'initial' in request.docstash: del request.docstash['initial'] # XXX: docstash will be saved further down form = SubmissionFormForm(request.POST or request.docstash.POST, initial=request.docstash.get('initial', {}).get('submission_form')) formsets = get_submission_formsets(request.POST or request.docstash.POST, initial=request.docstash.get('initial')) documents = Document.objects.filter(pk__in=request.docstash.get('document_pks', [])) protocol_uploaded = documents.filter(doctype__identifier='protocol').exists() if request.method == 'GET' and not request.docstash.POST and \ not 'initial' in request.docstash: protocol_uploaded = True # don't show error on completely new # submission # neither docstash nor POST data: this is a completely new submission # => prepopulate submitter_* fields with the presenters data profile = request.user.profile form.initial.update({ 'submitter_contact_first_name': request.user.first_name, 'submitter_contact_last_name': request.user.last_name, 'submitter_email': request.user.email, 'submitter_contact_gender': profile.gender, 'submitter_contact_title': profile.title, 'submitter_organisation': profile.organisation, 'submitter_jobtitle': profile.jobtitle, }) if 'notification_type_id' in request.docstash: notification_type = NotificationType.objects.get( id=request.docstash['notification_type_id']) else: notification_type = None if 'submission_id' in request.docstash: submission = Submission.objects.get( id=request.docstash['submission_id']) else: submission = None valid = False if request.method == 'POST': submit = request.POST.get('submit', False) save = request.POST.get('save', False) autosave = request.POST.get('autosave', False) request.docstash.name = ( request.POST.get('german_project_title') or request.POST.get('project_title') ) request.docstash.POST = request.POST request.docstash.save() if save or autosave: return HttpResponse('save successfull') for name, formset in formsets.items(): formset.full_clean() # work around django bug: full_clean does not get called in is_valid if total_form_count==0 formsets_valid = all([formset.is_valid() for formset in formsets.values()]) # non-lazy validation of formsets valid = form.is_valid() and formsets_valid and protocol_uploaded and not 'upload' in request.POST if valid and submission and not notification_type and \ not submission.current_submission_form.allows_resubmission(request.user): messages.error(request, _("This form can't be submitted at the moment.")) valid = False if submit and valid: if not submission: submission = Submission.objects.create() submission_form = form.save(commit=False) submission_form.submission = submission submission_form.is_notification_update = bool(notification_type) submission_form.is_transient = bool(notification_type) submission_form.save() form.save_m2m() submission_form.documents = documents for doc in documents: doc.parent_object = submission_form doc.save() investigators = formsets.pop('investigator').save(commit=False) employees = formsets.pop('investigatoremployee').save(commit=False) for investigator in investigators: investigator.submission_form = submission_form investigator.save() for employee in employees: if employee.investigator_index >= len(investigators): continue employee.investigator = investigators[employee.investigator_index] employee.save() for formset in formsets.values(): for instance in formset.save(commit=False): instance.submission_form = submission_form instance.save() request.docstash.delete() on_study_submit.send(Submission, submission=submission, form=submission_form, user=request.user) if notification_type: return redirect('notifications:create_diff_notification', submission_form_pk=submission_form.pk, notification_type_pk=notification_type.pk) return redirect('readonly_submission_form', submission_form_pk=submission_form.submission.current_submission_form.pk) context = { 'form': form, 'tabs': SUBMISSION_FORM_TABS, 'valid': valid, 'submission': submission, 'notification_type': notification_type, 'protocol_uploaded': protocol_uploaded, } for prefix, formset in formsets.items(): context['%s_formset' % prefix] = formset return render(request, 'submissions/form.html', context)
DocStash模型定义
class DocStash(models.Model): class ConcurrentModification(Exception): pass key = models.UUIDField(default=uuid.uuid4, primary_key=True) group = models.CharField(max_length=120, db_index=True, null=True) current_version = models.IntegerField(default=-1) owner = models.ForeignKey(User, on_delete=models.CASCADE) modtime = models.DateTimeField(auto_now=True) name = models.TextField(blank=True, null=True) value = JSONField(null=False) content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE) object_id = models.PositiveIntegerField(null=True) parent_object = GenericForeignKey('content_type', 'object_id') class Meta: app_label = 'docstash' unique_together = ('group', 'owner', 'content_type', 'object_id') #abstract = True def __getitem__(self, key): return self.value[key] def __setitem__(self, key, value): self.value[key] = value def __delitem__(self, key): del self.value[key] def __contains__(self, key): return key in self.value def get(self, name, default=None): return self.value.get(name, default) @property def POST(self): if 'POST' in self.value: return QueryDict(self.value['POST']) return None @POST.setter def POST(self, data): self.value['POST'] = data.urlencode() def save(self, **kwargs): if DocStash.objects.filter(key=self.key).exists(): updated = DocStash.objects.filter( key=self.key, current_version=self.current_version ).update(current_version=models.F('current_version') + 1) if not updated: raise self.ConcurrentModification() self.current_version += 1 return super().save(**kwargs)
已尝试的无效操作
- HTML代码无法读取
docstash.pk - 手动以字符串形式传入
docstash.pk可绕过错误,但无法找到对应页面
解决思路
1. 确保模板上下文存在有效的DocStash实例
错误提示docstash_key为空字符串,说明模板中的docstash对象要么未传入上下文,要么其key字段未正确生成。需检查dashboard视图(即渲染该HTML的视图)代码:
- 必须先创建并保存DocStash实例(调用
save()后UUID才会生成) - 将实例传入模板上下文:
# dashboard视图示例 def dashboard(request): docstash = DocStash.objects.create(owner=request.user) # 可预先设置初始数据到docstash.value return render(request, 'dashboard.html', {'docstash': docstash})
2. 匹配URL参数的约束
URL规则new/<str:docstash_key>/对应的正则是[^/]+,要求参数为非空字符串。空字符串无法匹配该规则,因此必须保证docstash.key是有效的UUID字符串(DocStash实例创建并保存后自动生成)。
3. 检查@with_docstash装饰器逻辑
该装饰器的作用应该是从URL参数docstash_key中获取DocStash实例。如果手动传入无效的pk字符串,装饰器会因找不到对应实例而返回404。需确认装饰器是否正确通过key字段查询实例:
# @with_docstash装饰器核心逻辑示例 def with_docstash(): def decorator(view_func): def _wrapped_view(request, docstash_key, *args, **kwargs): try: request.docstash = DocStash.objects.get(key=docstash_key, owner=request.user) except DocStash.DoesNotExist: raise Http404("DocStash not found") return view_func(request, *args, **kwargs) return _wrapped_view return decorator
4. 模板中增加空值防护
防止docstash为空或key无效时触发错误,在模板中添加判断:
{% if docstash and docstash.key %} <a class="dropdown-item" href="{% url 'core:create_submission_form' docstash_key=docstash.key %}">提交新表单</a> {% else %} <!-- 显示错误提示或禁用按钮 --> <span class="dropdown-item text-muted">无法创建提交表单</span> {% endif %}
内容的提问来源于stack exchange,提问作者Georges
相关产品推荐
相关产品推荐

