首页 > 解决方案 > 比较两个 Django 模型实例列表

问题描述

我有一个相当复杂的用例,我需要比较两个字典列表,但是列表可以处于多种不同的状态,理想情况下我希望能够在一个中处理所有这些状态。

这的背景是我有一个呈现为复选框表的表单,我试图获取表单的初始数据与cleaned_data进行更新时发送回的数据之间的差异(即选中复选框或未选中)。

因此,通常初始数据将仅包含选中复选框的数据,例如

initial_data = [<Event: Event object>, <Event: Event object>, <Event: Event object>, <Event: Event object>]

如果一个 Event 对象包含一个 id 和一个名称,它们可以是相同的,因为它可能是同一个事件。

如果未选中复选框,则cleaned_data可能如下所示:

cleaned_data = [<Event: Event object>, None, <Event: Event object>, None]

在这种情况下,我想从initial_data 中获取事件,其中cleaned_data 中没有。下一个用例是,如果选中复选框,则初始数据可能如下所示:

initial_data = [<Event: Event object>, <Event: Event object>]

然后像这样清理数据:

cleaned_data = [<Event: Event object>, <Event: Event object>, <Event: Event object>, <Event: Event object>]

在这种情况下,我想从cleaned_data 中返回与initial_data 不匹配的事件。

还有第三部分使事情复杂化,如果有一行没有选中的复选框,它也显示为无。所以我还需要处理:

initial_data = [<Event: Event object>, <Event: Event object>]

cleaned_data = [<Event: Event object>, None, <Event: Event object>, <Event: Event object>]

因此,为了澄清我想要做的是返回初始事件数据,如果表单中有相应的无,cleaned_data如果与初始数据有差异,则返回事件和无,cleaned_data并删除无。

我一直在尝试这样做:

events = [[x for x in cleaned_data if x not in initial_data], [x for x in initial_data if x not in cleaned_data]]

但这仅适用于我的一个用例(它适用于第二种情况,但第一种情况它返回 Nones 而不是事件)。

对此的任何帮助将不胜感激,因为我觉得我已经很接近了,但我无法在脑海中弄清楚!

谢谢你的时间。

PS这是Django应用程序的一部分

- 编辑 -

表单代码如下所示:

class ActivityGroupInline(InlineFormSet):
    model = models.ActivityGroup
    fields = ['events',]
    can_delete = False
    extra = 0

    @property
    def widgets(self):
        return {
            'events': widgets.CheckboxSelectMultiple(),
        }

    def get_factory_kwargs(self):
        kwargs = super().get_factory_kwargs()

        kwargs.update({
            'widgets': self.widgets,
        })

        return kwargs


class ActivityGroupEventLink(NamedFormsetsMixin, UpdateWithInlinesView):
    model = models.Container
    fields = []
    template_name = 'main/link_activitygroupsevents.html'
    success_url = reverse_lazy('dashboard')
    inlines = [ActivityGroupInline,]
    inlines_names = ['activitygroup_inline',]

    def get_object(self, queryset=None):
        return models.Container.objects.first()

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)

        context['breadcrumbs'] = [
            {'name': 'Home', 'url': reverse_lazy('dashboard')},
            {'name': 'Link Activities to Events', 'is_active': True}
        ]

        all_events = models.Event.objects.filter(
            container=context['object'],
        ).all()

        context['events'] = { event.id: event for event in all_events }

        for form in context['activitygroup_inline'].forms:
            activitygroup_kcs = set(form.instance.key_characteristics.all())

            potential_event_ids = set()

            for event in all_events:
                event_kcs = set(event.key_characteristics.all())

                if event_kcs & activitygroup_kcs:
                    potential_event_ids.add(event.id)

            form.fields['events'].choices = models.Event.objects.filter(
                id__in=list(potential_event_ids)
            ).order_by('id').values_list('id', 'name')

        return context

    def forms_valid(self, form, inlines):
        self.object = form.save()

        for formset in inlines:
            instances = formset.save(commit=False)

            for inst in instances:
                initial_events = [] # This is code I added to work with the solution that I am now trying
                new_events = []
                for f in formset:
                    initial_events.append(f.initial['events']) # This is code I added to work with the solution that I am now trying
                    new_events.append(f.cleaned_data['events'].first())

                flat_initial = [item for sublist in initial_events for item in sublist] # This is code I added to work with the solution that I am now trying

                try:
                    event = next(event for event in new_events if event is not None) # This is what I was previously doing to find the relevant event but it only works if there is one column in the table.
                except StopIteration:
                    raise ValidationError('There must be at least one Activity Group linked to an event.')

                activityevent = inst.activityevent_set.filter(activity_group_id=inst.id, event_id=event.id)

                if activityevent.exists():
                    activityevent.delete()
                else:
                    new_activityevent = models.ActivityEvent.objects.create(
                        activity_group=inst,
                        event=event,
                    )
                    new_activityevent.save()

                inst.save()

        return HttpResponseRedirect(self.get_success_url())

它还使用 django-extraforms。基本上,activity_group 和 event 有一个中间模型 activityevent,所以当建立一个新链接时,也需要建立一个新的 activityevent,并且在删除链接时需要删除它。

--EDIT2--

表单本身如下所示:

矩阵形式

我只关心这个问题的复选框

标签: pythondjango

解决方案


推荐阅读