Django 模型表单集在 POST 上非常慢

问题描述 投票:0回答:2

我试图允许用户在一个屏幕中修改多个记录,因此 Django Formsets 似乎是最好的选择。表单集中的每个表单都包含 9 个外键字段,这会导致运行许多查询。我通过对每个问题使用 Jquery Autocomplete 在表单渲染方面解决了这个问题,这将渲染时间从 30 或 40 秒减少到大约 3 或 4 秒(将数百个查询减少到 4 个)。我现在遇到的性能问题是在 POST 端 - 当提交表单时,它仍然根据 Django 调试工具栏运行数百个重复查询。我花了几个小时阅读各种博客和此处的表单集文档和建议。我尝试的最后一件事是将表单集查询集设置为自定义查询,并为每个外键字段使用 select_lated() 。根据 DjDT,这似乎确实使主查询包含大量联接,但它并没有消除 POST 上剩余的数百个查询。我已经把我所有的头发都拔出来了,所以也许有人有建议或者可以看看我做错了什么?

我的模特:

class Info(AbstractAddArchive):
    item= models.ForeignKey(ITEM, on_delete=models.PROTECT)
    rec= models.ForeignKey(Log, on_delete=models.PROTECT, null=True, blank=True)
    c1 = models.CharField(max_length=2, blank=True, null=True)
    c1rec = models.ForeignKey(Log, on_delete=models.PROTECT, related_name='c1rec', blank=True, null=True)
    c1sts = models.ForeignKey(Sts, on_delete=models.PROTECT, null=True, blank=True)
    c1f = models.IntegerField(blank=True, null=True)
    c2 = models.CharField(max_length=2, blank=True, null=True)
    c2rec = models.ForeignKey(Log, on_delete=models.PROTECT, related_name='c2rec', blank=True, null=True)
    c2sts = models.ForeignKey(Sts, on_delete=models.PROTECT, null=True, blank=True, related_name='c2sts')
    c2f = models.IntegerField(blank=True, null=True)
    c3 = models.CharField(max_length=2, blank=True, null=True)
    c3rec = models.ForeignKey(Log, on_delete=models.PROTECT, related_name='c3rec', blank=True, null=True)
    c3sts = models.ForeignKey(Sts, on_delete=models.PROTECT, null=True, blank=True, related_name='c3sts')
    c3f = models.IntegerField(blank=True, null=True)
    c4 = models.CharField(max_length=2, blank=True, null=True)
    c4rec = models.ForeignKey(Log, on_delete=models.PROTECT, related_name='c4rec', blank=True, null=True)
    c4sts = models.ForeignKey(Sts, on_delete=models.PROTECT, null=True, blank=True, related_name='c4sts')
    c4 = models.IntegerField(blank=True, null=True)
    comment1 = models.CharField(max_length=45, blank=True, null=True)
    comment2 = models.CharField(max_length=45, blank=True, null=True)
    posted_by = models.ForeignKey(User, on_delete=models.PROTECT, max_length=10, null=True, blank=True)
    posted_ts = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ('item',)  # This sets initial order of formset display

    def __str__(self):
        return f"{self.item_id}"

class Log(AbstractAddArchive):
    rec= models.CharField(max_length=10, primary_key=True)
    ... other fields ...
    slug = models.SlugField(null=False, blank=True, unique=True)

    def __str__(self):
            return f"{self.name}"

class Sts(models.Model):
    name = models.CharField(max_length=10, primary_key=True)
    description = models.CharField(max_length=50, null=True, blank=True)
    sequence = models.IntegerField()

    def __str__(self):
        return f"{self.name}"

我的表单和表单集:

class InfoReviseForm(forms.ModelForm):
    class Meta:
        model = Info
        fields = ('rec',
                  'c1rec',
                  'c1f',
                  'c1sts',
                  'c2rec',
                  'c2f',
                  'c2sts',
                  'c3rec',
                  'c3f',
                  'c3sts',
                  'c4rec',
                  'c4f',
                  'c4sts',
                  'comment1',
                  )
        widgets = {
            'rec': forms.TextInput(attrs={
                'class': 'autocomplete',       
                'placeholder': 'Choose rec...',
                'app': 'appname',
                'model-class': 'Log',
                'search-field': 'rec',
                'autocomplete-url': reverse_lazy('autocomplete_field')}),
            ... similar for all foreignkey fields - .js enables autocomplete on each
            }


class InfoBaseFormSet(forms.BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = Info.objects.select_related('rec').select_related('c1rec').select_related('c2rec').select_related('c3rec').select_related('c4rec').select_related('car').select_related('c1sts').select_related('c2sts').select_related('c3sts').select_related('c4sts')

InfoReviseFormset = modelformset_factory(Info, form=InfoReviseForm, extra=0, formset=InfoBaseFormSet)

我的看法(发帖方式)

def post(self, request, *args, **kwargs):
        formset = InfoReviseFormset(request.POST)
        if formset.is_valid():
            for form in formset:
                if form.has_changed():
                    carinfo_active = form.instance
                    carinfo_active.posted_by = request.user
                    carinfo_active.save()  #save new data - updates existing non archive record
            return super().post(request, *args, **kwargs)

据我所知,“if formset.is_valid”是很多查询运行的地方 - 但之后它也很慢。顺便说一句,目前我的表单集中大约有 55 个表单 - 表单的最大数量可能高达 200 个,这只会进一步减慢速度。如果我可以减少查询,我认为这会运行得更合理。

我简化了上面的一些代码 - 如果缺少任何内容可以帮助查看,我很乐意发布更多内容。

感谢您的建议

python django django-models django-forms django-templates
2个回答
2
投票

性能不佳可能出现在与 formset.is_valid() 相关且与您的查询集相关的

View
中。尝试将查询集定义从表单的类定义移动到视图中的显式标注。

def post(self, request, *args, **kwargs):
    myquery = Info.objects.select_related('rec').select_related('c1rec').select_related('c2rec').select_related('c3rec').select_related('c4rec').select_related('car').select_related('c1sts').select_related('c2sts').select_related('c3sts').select_related('c4sts')
    formset = InfoReviseFormset(request.POST, queryset=myquery)
    if formset.is_valid():
        for form in formset:
            if form.has_changed():
                carinfo_active = form.instance
                carinfo_active.posted_by = request.user
                carinfo_active.save()  #save new data - updates existing non archive record
        return super().post(request, *args, **kwargs)

在类似的情况下,我发现随着项目中模型实例总数的增长,我的保存时间变得越来越长。为了调试这个问题,我测量了每行代码的时间,并将问题隔离到

formset.is_valid()
。通过添加显式查询集,保存时间从每个表单集 10 秒减少到不到半秒。

示例:

assyform = AssyModelFormset(request.POST, request.FILES, prefix='assy', queryset=assembly_query)

0
投票

我在使用 formset 时遇到类似的问题,并使用 django-debug-toolbar 来查看正在进行的查询量。

© www.soinside.com 2019 - 2024. All rights reserved.