Django ManyToMany Validation Constraint

I have a ManyToMany link, and a Foreign key which links three objects.

[A]>--<[B]>---[C]

A can belong to many of B, and vice versa. However, A can only belong to B objects with the same parent C.

I'm trying to do something in the clean() method of the model. I'm using Django Rest Framework and no ModelForms or anything like that. I haven't been able to figure it out yet

Simplified Sample Code

class Device(models.Model):
    name = models.CharField(max_length=20)
    projects = models.ManyToManyField(Project, 'devices')
    details = models.CharField(max_length=200)
    serial = models.CharField(max_length=20)
    address models.GenericIPAddressField(default="0.0.0.0")
    port = models.IntegerField(default=3000)
    jumpers = models.IntegerField(default=0)
    install_date = models.DateField(blank=True, null=True)

class Project(models.Model):
    name = models.CharField(max_length=20)
    description = models.CharField(max_length=250)
    area = models.ForeignKey(Area)

class Area(models.Model):
    name = models.CharField(max_length=20)
    description = models.CharField(max_length=250)
    owner = models.CharField(max_length=20)  # microservice doesn't have owner group - field in JWT

Serializers

class AreaSerializer(serializers.ModelSerializer):

    class Meta:
        model = Area
        fields = ('name', 'description', 'owner')


class ProjectSerializer(serializers.ModelSerializer):

    class Meta:
        model = Project
        fields = ('id', 'name', 'description', 'area')


class DeviceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Device
        fields = ('id', 'name', 'projects', 'details', 'serial',
                  'address', 'port', 'jumpers', 'install_date')

I am not sure where and how do you want to validate your data. So I am just posting the method which can validate if a project can be linked to a device or not based on your specific check.

def validate_project(device, project):
    projects = device.projects.all()
    areas = set(projects.values_list('area', flat=True))
    if len(areas) > 1:
        raise serializers.ValidationError('projects are not valid')        
    return areas.pop() == project.area_id

EDIT:

You have to use a intermediate model for storing the relationship between device and project.

class Membership(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    area = models.ForeignKey(Area, on_delete=models.CASCADE)

use the above membership model to store the many to many relations.

On your device model use this field to define the many to many relation.

projects = models.ManyToManyField(Project, through='Membership')

checkout the docs

Now when you link a device and project you will have explicitly add the area id as well. Before adding now you can check if the project is valid or not based on the area associated.


(ignore the wonky field types, cba) 在这里输入图像描述

What it boils down to is: you need a table BC that stores relations between B and C . Table A would then select only from those relations through the intermediary m2m table ABC (or ditch ABC, couldn't figure out how to draw m2m with the online tool). I think I mixed up B and C in this picture, swap them around depending on whether B or C holds the ForeignKey.
Please correct if I'm wrong!

链接地址: http://www.djcxy.com/p/41210.html

上一篇: 从Flask运行Scrapy

下一篇: Django ManyToMany验证约束