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!
上一篇: 从Flask运行Scrapy