如何保存具有直通关系的多对多字段

Posted

技术标签:

【中文标题】如何保存具有直通关系的多对多字段【英文标题】:How to save a ManyToMany field with a through relationship 【发布时间】:2015-10-30 00:07:43 【问题描述】:

我有以下具有ManyToManythrough 关系的模型:

class Meeting(models.Model):
    site = models.ForeignKey(Site)
    meeting_title = models.CharField(default='', max_length=128, blank=True, null=True)
    meeting_visitors = models.ManyToManyField(Visitor, through="MeetingArrival", blank=False, null=False) 

class Visitor(models.Model):
    visitor_company = models.ForeignKey(Company)
    visitor_name = models.CharField(default='', max_length=128, blank=False, null=False)

class MeetingArrival(models.Model):
    visitor = models.ForeignKey(Visitor)
    meeting = models.ForeignKey(Meeting)
    arrival_status = models.BooleanField(default=False)

我有一个创建会议的表格:

class AddMeetingForm(forms.ModelForm):

    class Meta:
        model = Meeting
        exclude = ['site',]

以及保存表单的简单视图:

def add_meeting(request): 
    add_meeting_form = AddMeetingForm(request.POST or None)
    site = Site.objects.get(user=request.user.id)

    if request.method == "POST":
        if add_meeting_form.is_valid():

            obj = add_meeting_form.save(commit=False)
            obj.site = site
            obj.save()

这会保存表单,但不会保存 meeting_visitors 字段,即使此字段在视图中完美呈现。我该如何挽救这段关系?

编辑

如果我将add_meeting_form.save_m2m() 添加到视图中,我会得到Cannot set values on a ManyToManyField which specifies an intermediary model. Use meetings.MeetingArrival's Manager instead.。我该怎么做?

【问题讨论】:

【参考方案1】:

您必须在视图中显式保存MeetingArrival 对象,以在ManyToManyField 带有through 参数的情况下保存中间模型。

对于 Django 2.1 及以下版本,如果 ManyToManyField 带有中间模型,则不能使用普通多对多字段可用的 addcreateassignment

根据Django 1.8 docs:

与普通的多对多字段不同,您不能使用 add、create 或 创建关系的任务。

创建这种关系的唯一方法是创建 中间模型的实例。

因此,您必须在视图中显式创建 MeetingArrival 对象。

你可以这样做:

def add_meeting(request): 
    add_meeting_form = AddMeetingForm(request.POST or None)
    site = Site.objects.get(user=request.user.id)

    if request.method == "POST":
        if add_meeting_form.is_valid():
            obj = add_meeting_form.save(commit=False)
            obj.site = site
            obj.save()

            # create an instance of 'MeetingArrival' object
            meeting_arrival_obj = MeetingArrival(meeting=obj, visitor=<your_visitor_object_here>, arrival_status=True)
            meeting_arrival_obj.save() # save the object in the db

【讨论】:

谢谢,我如何获得your_visitor_object_here?表单输入是多选。 我们可以编写逻辑来获取视图中的访问者对象。由于Visitor 是外键,因此您可以找到哪个MeetingArrival 与哪个Visitor 相关,然后使用该Visitor 创建一个MeetingArrival 对象。 谢谢。如果我有另一个不是through 字段的ManyToMany 字段,那么我将如何保存表单?例如` meeting_team_members = models.ManyToManyField(Team, blank=False, null=False)` 您只需通过.add() 即可做到这一点。假设ABCmeeting_team_members 的模型。然后你可以做abc_object.meeting_team_members.add(team_object)【参考方案2】:

对于 django 1.x,正如 Rahul 所说,您不能使用 addcreate

对于 django 2.x,您实际上可以在此处查看文档 django 2.x

您也可以使用 add()、create() 或 set() 来创建关系,只要您为任何必填字段指定 through_defaults:

beatles.members.add(john, through_defaults='date_joined': date(1960, 8, 1))
beatles.members.create(name="George Harrison", through_defaults='date_joined': date(1960, 8, 1))
beatles.members.set([john, paul, ringo, george], through_defaults='date_joined': date(1960, 8, 1))

【讨论】:

【参考方案3】:

使用表时,需要手动保存。

MeetingArrival.objects.create( ... )

【讨论】:

谢谢,可以举个例子吗?【参考方案4】:

不要将处理 ManyToManyField 的逻辑放在视图中,而是将其放在表单中,这样如果您在多个地方使用表单,就不必重复自己。

为此,您需要重写ModelFormsave 方法。

查看我对另一个问题的更全面的回答:https://***.com/a/40822731/2863603

【讨论】:

【参考方案5】:

我是这样做的,仍然试图从选择中获取多个 id 值,我认为我的 javascript 有问题

但这里是python代码:

class ArticuloCreateView(View):
    def __init__(self):
        self.template_name = 'articulo/formulario.html'

    def get(self, request):
        formulario = ArticuloForm()
        contexto = 
            'form': formulario,
            'operation': "Nuevo"
        
        return render(request, self.template_name, contexto)

    @transaction.atomic
    def post(self, request):
        punto_transaccion = transaction.savepoint()
        formulario = ArticuloForm(request.POST)
        almacenes = request.POST.get('almacenes', 0)
        almacenes = Almacen.objects.filter(id=almacenes)

        if formulario.is_valid():
            datos_formulario = formulario.cleaned_data
            articulo = Articulo()
            articulo.clave = datos_formulario.get('clave')
            articulo.descripcion = datos_formulario.get('descripcion')
            articulo.tipo = datos_formulario.get('tipo')
            articulo.udm = datos_formulario.get('udm')
            articulo.clave_jde = datos_formulario.get('clave_jde')
            articulo.save()

            for almacen in almacenes:
                Stock.objects.create(articulo=articulo, almacen=almacen)
            if punto_transaccion:
                transaction.savepoint_commit(punto_transaccion)

            return redirect(
                reverse('inventarios.articulos_lista')
            )
        contexto = 
            'form': formulario,
        

        return render(request, self.template_name, contexto)

【讨论】:

【参考方案6】:

user_profiles = UserProfile.objects.all()

NotificationUser.objects.bulk_create([NotificationUser(user=user_profile, notification=notification) for user_profiles in user_profiles])

【讨论】:

以上是关于如何保存具有直通关系的多对多字段的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 上的多对多问题关系

Android Room - 带有附加字段的多对多关系

如何在 django 中处理未保存的多对多关系?

Fluent NHibernate:如何在关系表上映射具有附加属性的多对多关系?

具有多对多和直通表的 Graphene-django

如何在 Ruby on Rails 中管理嵌套字段的多对多关系