0
votes

I have the following models.

class ServerGroup(models.Model):
    name = models.SlugField(unique=True)
    factor = models.IntegerField()

class ServerGroupMember(models.Model):
    class Meta:
        unique_together = (
            ("server_group", "position"),
            ("server_group", "server"),
        )

    position = models.IntegerField()
    server_group = models.ForeignKey(
        "ServerGroup", related_name="servers", on_delete=models.CASCADE
    )
    server = models.ForeignKey("Server", on_delete=models.CASCADE)

A ServerGroup has a couple of properties, name and factor, and a collection of ServerGroupMember objects. Each ServerGroupMember object, contains an integer position and a reference to a Server object. For a given ServerGroup the position must be unique, and for a given ServerGroup the server must be unique. However, globally, the position and server objects do not have to be unique, as in 2 ServerGroups may contain a server at postion 1, and the same Server may appear in multiple Server Groups, just not multiple times in the same Server Group.

Given that I have the following serializers, how can I validate the above? The model currently does validate the condition at the database level, but will raise a unique constraint error if I attempt to violate it. What I want is to be able to detect this in my views, such that I can return an appropriate validation error message response before it has a chance to hit the DB and raise that exception.

class ServerGroupMemberSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.ServerGroupMember
        fields = ("position", "server")
    server = serializers.SlugRelatedField(
        slug_name="name", queryset=models.Server.objects.all()
    )

class SrvereGroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.ServerrGroup
        fields = ("name", "factor", "servers")
    servers = ServerGroupMemberSerializer(many=True, required=False)

    def create(self, validated_data): ...
    def update(self, validated_data): ...
2

2 Answers

1
votes

You can alwas check your validations when overriding the clean() method in your model, or also validating in serializers.py. Also, the validation only concerns your not ForeignKey fields, since you can't add a ServerGroupMember member_one twice to a ServerGroup server_test.

class ServerGroupMember(models.Model):
    class Meta:
        unique_together = (
            ("server_group", "position"),
            ("server_group", "server"),
        )

    position = models.IntegerField()
    server_group = models.ForeignKey(
        "ServerGroup", related_name="servers", on_delete=models.CASCADE
    )
    server = models.ForeignKey("Server", on_delete=models.CASCADE)

   def clean(self):
       super().clean()
       if self.position in ServerGroup.server_group_set.all().values_list('position', flat=True):
           raise ValidationError(f"Position {self.position} already exists for ServerGroup {self.server_group.name}")
0
votes

I figured out one way to do this, but still curious if this is the best approach here. This seems to work fine for creating new ServerGroupMember instances. Although not sure if it will work as well for the Update case, that is yet to be tried.

In the views, while instantiating the serializer, I passed to the constructor of the serializer, a context object containing the ServerGroup name. E.g.

def post(self, request: Request, name: str, format=None) -> Response:
    server_group = self.get_object()  # defined elsewhere
    serializer = serializers.ServerGroupMemberSerializer(
        data=request.data, context={"server_group": server_group.name}
    )
    ...

Then in the ServerGroupMemberSerializer I added field-level validators, e.g.

def validate_position(self, value):
    server_group = self.context.get("server_group")
    try:
        models.ServerGroupMember.objects.get(
            server_group__name=server_group, position=position
        )
    except models.ServerGroupMember.DoesNotExist:
        return value
    raise serializers.ValidationError(
        f"A server group member with position {position} already exists"
    )

def validate_server(self, value):
    ... # Follow same pattern as above