DRF: Allow all fields in a GET request, but restrict POST to only one field

Let us illustrate this with an example.

Let's say I want to create the FileUploader API where it will store fields such as id, file_path, file_name, size, owner, etc. in the database. See an example model below:

class FileUploader(models.Model): file = models.FileField() name = models.CharField(max_length=100) #name is filename without extension version = models.IntegerField(default=0) upload_date = models.DateTimeField(auto_now=True, db_index=True) owner = models.ForeignKey('auth.User', related_name='uploaded_files') size = models.IntegerField(default=0) 

Now, for the API, this is what I want:

  • GET: When I run the GET endpoint, I want all of the above fields to be loaded for each downloaded file.

  • POST: But so that the user can create / upload a file, why should she worry about passing all these fields. It can just load the file, and then, I suppose, the serializer can get the remaining fields from the loaded FILE.

Searilizer: Question: I created a serializer below to serve my purpose. But not sure if its the right way to implement.

 class FileUploaderSerializer(serializers.ModelSerializer): #overwrite = serializers.BooleanField() class Meta: model = FileUploader fields = ('file','name','version','upload_date', 'size') read_only_fields = ('name','version','owner','upload_date', 'size') def create(self, validated_data): return FileUploader.objects.create(**validated_data) 

Permission for reference:

 class FileUploaderViewSet(viewsets.ModelViewSet): serializer_class = FileUploaderSerializer parser_classes = (MultiPartParser, FormParser,) # overriding default query set queryset = LayerFile.objects.all() def get_queryset(self, *args, **kwargs): qs = super(FileUploaderViewSet, self).get_queryset(*args, **kwargs) qs = qs.filter(owner=self.request.user) return qs 

Also, another question: I want the user to provide an additional parameter called 'overwrite' (if the file already exists on the server).

I am not sure how to access this in a serializer.

+2
source share
1 answer

IMHO, few serializers will only create more confusion.

@AaronLelevier - I reviewed another stackoverflow solution, as you suggested. But finally, I decided to implement my own clean solution.

I would prefer the solution below:

  • Do not change your appearance (leave it as default)
  • Add the .validate () method to your serializer; along with other necessary .create or .update (), etc. Here the real logic will go in the validate () method. Where, depending on the type of request, we will create a validated_data dict, as required by our serializer.

I think this is the cleanest approach.

Code example: (modified serializer.py, views.py remains unchanged)

 class LayerFileSerializer(serializers.ModelSerializer): class Meta: model = LayerFile fields = ('id', 'file','name','version','upload_date', 'size', 'maps') read_only_fields = ('name','version','owner','upload_date', 'size', 'maps') def validate(self, validated_data): if self.context['request'].method == 'PATCH': # catch here: validated_data only contains filed that are valid for serializer # for post/update/patch method only valid field is the file # but we need 'name' field as well so trick is to get name from the self.context[request].data validated_data['name'] = self.context['request'].data.get('name', None) if validated_data['name'] is None or validated_data['name'] == '': raise serializers.ValidationError("'name' field cannot be empty!") return validated_data validated_data['owner'] = self.context['request'].user validated_data['name'] = os.path.splitext(validated_data['file'].name)[0] validated_data['size'] = validated_data['file'].size #print self.context['request'].overwrite log.debug("serialized layer data: %s" %validated_data) try: layer_obj = LayerFile.objects.get(owner=validated_data['owner'], name=validated_data['name']) except LayerFile.DoesNotExist: layer_obj = None if layer_obj: raise serializers.ValidationError('Layer with same name already exist. Use overwrite flag to overwrite it.') return validated_data # This will handle rename def partial_update(self, instance, validated_data): instance.name = validated_data['name'] return instance # this will handle POST - or layer upload def create(self, validated_data): return LayerFile.objects.create(**validated_data) 
+4
source

All Articles