Thorvald
Thorvald

Reputation: 665

Django (DRF) select related on is_valid()?

My problem is that when trying to run is_valid() on a big chunk of data in a POST-request, where the model has a foreign key, it will fetch the foreign key table for each incoming item it needs to validate.

This thread describes this as well but ended up finding no answer: Django REST Framework Serialization POST is slow

This is what the debug toolbar shows:

enter image description here

My question is therefore, is there any way to run some kind of select_related on the validation? I've tried turning off validation but the toolbar still tells me that queries are being made.

These are my models:

class ActiveApartment(models.Model):
    adress = models.CharField(default="", max_length=2000, primary_key=True)
    company = models.ForeignKey(Company, on_delete=models.CASCADE)

class Company(models.Model):
    name = models.CharField(blank=True, null=True, max_length=150)

These are my serializers: I have tried not using the explicit PrimaryKeyRelatedField as well, having validators as [] doesn't seem to stop the validation either for some reason.

class ActiveApartmentSerializer(serializers.ModelSerializer):
    company = serializers.PrimaryKeyRelatedField(queryset=Company.objects.all())

    class Meta:
        model = ActiveApartment
        list_serializer_class = ActiveApartmentListSerializer
        fields = '__all__'
      
        extra_kwargs = {
            'company': {'validators': []},
        }

class ActiveApartmentListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        data = [ActiveApartment(**item) for item in validated_data]
        # Ignore conflcits is the only "original" part of this create method
        return ActiveApartment.objects.bulk_create(data, ignore_conflicts=True)

    def update(self, instance, validated_data):
        pass

This is my view:

def post(self, request, format=None):
    
    # Example of incoming data in request.data
    dummydata = [{"company": 12, "adress": "randomdata1"}, {"company": 12, "adress": "randomdata2"}]

    serializer = ActiveApartmentSerializer(data=request.data, many=True)
    
    # This will run a query to check the validity of their foreign keys for each item in dummydata
    if new_apartment_serializer.is_valid():
        print("valid")

Any help would be appreciated (I would prefer not to use viewsets)

Upvotes: 2

Views: 881

Answers (2)

Thorvald
Thorvald

Reputation: 665

The way I solved it was I took the direction athanasp pointed me in and tweaked it a bit as his solution was close but not working entirely.

I:

  1. Created a simple nested serializer
  2. Used the init-method to make the query
  3. Each item checks the list of companies in its own validate method
class CitySerializer(serializers.ModelSerializer):
        class Meta:
        model = City
        fields = ('name',)
class ListingSerializer(serializers.ModelSerializer):
    # city = serializers.IntegerField(required=True, source="city_id") other alternative
    city = CitySerializer()

     def __init__(self, *args, **kwargs):
          self.cities = City.objects.all()
          super().__init__(*args, **kwargs)

    def validate_city(self, value):
    try:
        city = next(item for item in self.cities if item.name == value['name'])
    except StopIteration:
        raise ValidationError('No city')
    return city

And this is how the data looks like that should be added:

city":{"name":"stockhsdolm"}

Note that this method more or less works using the IntegerField() suggested by athan as well, the difference is I wanted to use a string when I post the item and not the primary key (e.g 1) which is used by default. By using something like city = serializers.IntegerField(required=True, source="city_id") works as well, just tweak the validate method, e.g fetching all the Id's from the model using values_list('id', flat=True) and then simply checking that list for each item.

Upvotes: 0

athanasp
athanasp

Reputation: 233

Have you tried to define company as IntegerField in the serializer, pass in the view's context the company IDs and add a validation method in field level?

class ActiveApartmentSerializer(serializers.ModelSerializer):
    company = serializers.IntegerField(required=True)

    ...

    def __init__(self, *args, **kwargs):
        self.company_ids = kwargs.pop('company_ids', None)
        super().__init__(*args, **kwargs)
   
    def validate_company(self, company):
        if company not in self.company_ids:
            raise serializers.ValidationError('...') 
        return company  

Upvotes: 1

Related Questions