get_or_create is a frequently used and handy function in Django. In Django DRF you can use it as you would use in a regular application if using functional view or class view for processing the data.

With Django Serializes offering many functionality in the box, I wanted to get this done in serialize level. It’s staright forward if you don’t have any unique_together constrain set to the key fields.

For demo I am using a Guest Model with name, phone number and email, where email and phone is set to be unique_togethe.


class Guest(models.Model):
  first_name = models.CharField(max_length=300)
  last_name = models.CharField(max_length=300, blank=True)
  phone = models.CharField(max_length=300)
  email = models.EmailField(max_length=300)
  
  class Meta:
    unique_together = ('email', 'phone')

Define a new create function and use get_or_create. You are good to go.


class GuestSerializer(serializers.ModelSerializer):
  last_name = serializers.CharField(required=False)
  id = serializers.ReadOnlyField()
  class Meta:
    model = Guest
    fields = ('id','first_name', 'last_name', 'email', 'phone')

  def create(self, validated_data):
    guest, created = Guest.objects.get_or_create(
      email=validated_data['email'], 
      phone=validated_data['phone'], 
      defaults={
        'first_name': validated_data['first_name'], 
        'last_name': validated_data['last_name']
      })
  return guest

unique_together and get_or_create in DRF

If you have set up unique_together constrains for fields in models then the above code will raise a unique together error before the create functions is invoked.

It’s triggered by validation functions in ModelSerializer which inspects unique together before passing control to create function, to get over this a custom validation function to skip the unique together validation will work, because get_or_create is used.


class GuestSerializer(serializers.ModelSerializer):
  last_name = serializers.CharField(required=False)
  id = serializers.ReadOnlyField()
  class Meta:
    model = Guest
    fields = ('id','first_name', 'last_name', 'email', 'phone')

  def run_validators(self, value):
    for validator in copy(self.validators):
      if isinstance(validator, validators.UniqueTogetherValidator):
        self.validators.remove(validator)
    super(GuestSerializer, self).run_validators(value)

  def create(self, validated_data):
    guest, created = Guest.objects.get_or_create(property=validated_data['property'], 
      email=validated_data['email'], 
      phone=validated_data['phone'], 
      defaults={
        'first_name': validated_data['first_name'], 
        'last_name': validated_data['last_name']
      })
  return guest