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

解决Django添加Message时主键值缺失报错问题

Django后台添加Message时报错:‘Message' instance needs to have a primary key value before this relationship can be used

问题背景

在Django后台创建Message实例时触发上述错误,已尝试以下操作但未解决:

  • 添加了自定义主键字段id = models.BigAutoField(primary_key=True, auto_created=True)
  • 验证SQLite数据库中主键自增结构正常
  • 参考过Stack Overflow和GitHub上的常规修复方案

怀疑是Django/Python版本更新导致的兼容性问题,考虑修改Message与Article的关联方式,但因是Jazzband Newsletter的原生设计,暂时犹豫是否调整。

相关代码

models.py中的Message类

class Message(models.Model):
    """ Message as sent through a Submission. """
    
    default_id = time.time()
    #adding primary key to satisfy error
    id = models.BigAutoField(primary_key=True, auto_created=True)
    title = models.CharField(max_length=200, verbose_name=_('title'))
    slug = models.SlugField(verbose_name=_('slug'))

    newsletter = models.ForeignKey(
        Newsletter, verbose_name=_('newsletter'), on_delete=models.CASCADE, default=get_default_newsletter
    )

    date_create = models.DateTimeField(
        verbose_name=_('created'), auto_now_add=True, editable=False
    )
    date_modify = models.DateTimeField(
        verbose_name=_('modified'), auto_now=True, editable=False
    )

    class Meta:
        verbose_name = _('message')
        verbose_name_plural = _('messages')
        unique_together = ('slug', 'newsletter')
        #order_with_respect_to = 'newsletter'

    def __str__(self):
        try:
            return _("%(title)s in %(newsletter)s") % {
                'title': self.title,
                'newsletter': self.newsletter
            }
        except Newsletter.DoesNotExist:
            logger.warning('No newsletter has been set for this message yet.')
            return self.title

    def get_next_article_sortorder(self):
        """ Get next available sortorder for Article. """

        next_order = self.articles.aggregate(
            models.Max('sortorder')
        )['sortorder__max']

        if next_order:
            return next_order + 10
        else:
            return 10

    @cached_property
    def _templates(self):
        """Return a (subject_template, text_template, html_template) tuple."""
        return self.newsletter.get_templates('message')

    @property
    def subject_template(self):
        return self._templates[0]

    @property
    def text_template(self):
        return self._templates[1]

    @property
    def html_template(self):
        return self._templates[2]

    @classmethod
    def get_default(cls):
        try:
            return cls.objects.order_by('-date_create').all()[0]
        except IndexError:
            return None

admin.py中的MessageAdmin类

class MessageAdmin(NewsletterAdminLinkMixin, ExtendibleModelAdminMixin,
                   admin.ModelAdmin):
    logger.critical('MessageAdmin')
    save_as = True
    list_display = (
        'admin_title', 'admin_newsletter', 'admin_preview', 'date_create',
        'date_modify'
    )
    list_filter = ('newsletter', )
    date_hierarchy = 'date_create'
    prepopulated_fields = {'slug': ('title',)}

    inlines = [ArticleInline, AttachmentInline, ]

    """ List extensions """
    def admin_title(self, obj):
        return format_html('<a href="{}/">{}</a>', obj.id, obj.title)
    admin_title.short_description = _('message')

    def admin_preview(self, obj):
        url = reverse('admin:' + self._view_name('preview'), args=(obj.id,),
                      current_app=self.admin_site.name)
        return format_html('<a href="{}">{}</a>', url, _("Preview"))
    admin_preview.short_description = ''

    """ Views """
    def preview(self, request, object_id):
        logger.critical('preview object_id = %s', object_id)
        return render(
            request,
            "admin/newsletter/message/preview.html",
            {'message': self._getobj(request, object_id),
             'attachments': Attachment.objects.filter(message_id=object_id)},
        )

    @xframe_options_sameorigin
    def preview_html(self, request, object_id):
        logger.critical('preview_html object_id = %s', object_id)
        message = self._getobj(request, object_id)

        if not message.html_template:
            raise Http404(_(
                'No HTML template associated with the newsletter this '
                'message belongs to.'
            ))

        c = {
            'message': message,
            'site': Site.objects.get_current(),
            'newsletter': message.newsletter,
            'date': now(),
            'STATIC_URL': settings.STATIC_URL,
            'MEDIA_URL': settings.MEDIA_URL
        }

        return HttpResponse(message.html_template.render(c))

    @xframe_options_sameorigin
    def preview_text(self, request, object_id):
        message = self._getobj(request, object_id)

        c = {
            'message': message,
            'site': Site.objects.get_current(),
            'newsletter': message.newsletter,
            'date': now(),
            'STATIC_URL': settings.STATIC_URL,
            'MEDIA_URL': settings.MEDIA_URL
        }

        return HttpResponse(
            message.text_template.render(c),
            content_type='text/plain'
        )

    def submit(self, request, object_id):
        submission = Submission.from_message(self._getobj(request, object_id))

        change_url = reverse(
            'admin:newsletter_submission_change', args=[submission.id])

        return HttpResponseRedirect(change_url)

    def subscribers_json(self, request, object_id):
        message = self._getobj(request, object_id)

        json = serializers.serialize(
            "json", message.newsletter.get_subscriptions(), fields=()
        )
        return HttpResponse(json, content_type='application/json')

    """ URLs """
    def get_urls(self):
        urls = super().get_urls()

        my_urls = [
            path('<object_id>/preview/',
                 self._wrap(self.preview),
                 name=self._view_name('preview')),
            path('<object_id>/preview/html/',
                 self._wrap(self.preview_html),
                 name=self._view_name('preview_html')),
            path('<object_id>/preview/text/',
                 self._wrap(self.preview_text),
                 name=self._view_name('preview_text')),
            path('<object_id>/submit/',
                 self._wrap(self.submit),
                 name=self._view_name('submit')),
            path('<object_id>/subscribers/json/',
                 self._wrap(self.subscribers_json),
                 name=self._view_name('subscribers_json')),
        ]

        return my_urls + urls

修复方案

1. 修复get_next_article_sortorder方法

错误核心是:Message实例未保存(无主键)时,该方法就尝试查询关联的articles集合。修改方法,先判断实例是否已保存:

def get_next_article_sortorder(self):
    """ Get next available sortorder for Article. """
    # 实例未保存时直接返回默认排序值
    if not self.pk:
        return 10
    next_order = self.articles.aggregate(
        models.Max('sortorder')
    )['sortorder__max']

    if next_order:
        return next_order + 10
    else:
        return 10

2. 调整Admin Inline配置

检查ArticleInline的extra参数,避免默认生成过多空表单触发关联查询:

class ArticleInline(admin.TabularInline):
    model = Article
    extra = 0  # 减少默认空表单数量,避免提前触发关联逻辑

3. 移除自定义主键(可选)

Django默认会自动生成自增主键id,手动添加的BigAutoField可能和默认逻辑冲突,尝试移除该字段后重新执行迁移:

# 删除models.py中的这一行
# id = models.BigAutoField(primary_key=True, auto_created=True)

4. 验证版本兼容性

检查当前Django版本与Jazzband Newsletter的兼容情况,优先升级Jazzband Newsletter到对应Django版本的最新稳定版,避免版本差异引发的隐性bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:50:29