Saturday, May 7, 2016

Get params validation on viewsets.ModelViewSet

Leave a Comment

I am new to django and building a REST API using django-rest-framework. I have written some code to check whether the user has supplied some parameters or not.But that is very ugly with lot of if conditions, so i want to refactor it.Below is the code that i have written please suggest how to refactor it.

I am looking for some django based validations.

class AssetsViewSet(viewsets.ModelViewSet):     queryset = Assets.objects.using("gpr").all()   def create(self, request):     assets = []     farming_details = {}      bluenumberid = request.data.get('bluenumberid', None)     if not bluenumberid:         return Response({'error': 'BlueNumber is required.'})      actorid = request.data.get('actorid', None)     if not actorid:         return Response({'error': 'Actorid is required.'})      asset_details = request.data.get('asset_details', None)     if not asset_details:         return Response({'error': 'AssetDetails is required.'})      for asset_detail in asset_details:         location = asset_detail.get('location', None)       if not location:         return Response({'error': 'location details is required.'})        assettype = asset_detail.get('type', None)       if not assettype:         return Response({'error': 'assettype is required.'})        asset_relationship = asset_detail.get('asset_relationship', None)       if not asset_relationship:         return Response({'error': 'asset_relationship is required.'})        subdivision_code = location.get('subdivision_code', None)       if not subdivision_code:         return Response({'error': 'subdivision_code is required.'})        country_code = location.get('country_code', None)       if not country_code:         return Response({'error': 'country_code is required.'})        locationtype = location.get('locationtype', None)       if not locationtype:         return Response({'error': 'locationtype is required.'})        latitude = location.get('latitude', None)       if not latitude:         return Response({'error': 'latitude is required.'})        longitude = location.get('longitude', None)       if not longitude:         return Response({'error': 'longitude is required.'})        try:         country_instance = Countries.objects.using('gpr').get(countrycode=country_code)       except:         return Response({'error': 'Unable to find country with countrycode ' + str(country_code)})       try:         subdivision_instance = NationalSubdivisions.objects.using('gpr').get(subdivisioncode=subdivision_code, countrycode=country_code)       except:           return Response({'error': 'Unable to find subdivision with countrycode ' + str(country_code) + ' and' + ' subdivisioncode ' + str(subdivision_code)})        kwargs = {}       kwargs['pobox'] = location.get('pobox', '')       kwargs['sublocation'] = location.get('sublocation', '')       kwargs['streetaddressone'] = location.get('streetaddressone', '')       kwargs['streetaddresstwo'] = location.get('streetaddresstwo', '')       kwargs['streetaddressthree'] = location.get('streetaddressthree', '')       kwargs['city'] = location.get('city', '')       kwargs['postalcode'] = location.get('postalcode', '')        cursor = connections['gpr'].cursor()       cursor.execute("Select uuid() as uuid")       u = cursor.fetchall()       uuid = u[0][0].replace("-", "")        kwargs['locationid'] = uuid     #   l.refresh_from_db()       try:         Locations.objects.using('gpr').create_location(locationtype=locationtype, latitude=latitude, longitude=longitude, countrycode=country_instance, subdivisioncode = subdivision_instance, **kwargs)       except (TypeError, ValueError):          return Response({'error': 'Error while saving location'})        try:         location_entry = Locations.objects.using('gpr').get(locationid=uuid)       except:         return Response({'error': 'Unable to find location with locationid ' + str(uuid)})        asset_entry = Assets.objects.using('gpr').create(locationid=location_entry, assettype=assettype)       asset_entry = Assets.objects.using('gpr').filter(locationid=location_entry, assettype=assettype).latest('assetinserted')       farming_details[asset_entry.assetid] = []        try:         actor = Actors.objects.using('gpr').get(actorid = actorid)       except:         return Response({'error': 'Unable to find actor with actorid ' + str(actorid)})       assetrelationship = AssetRelationships.objects.using('gpr').create(assetid= asset_entry, actorid= actor,assetrelationship=asset_relationship)       assets.append(asset_entry)        if assettype=="Farm or pasture land":             hectares = asset_detail.get('hectares', None)             if hectares is None:               return Response({'error': 'hectares must be a decimal number'})             try:               farmingasset = FarmingAssets.objects.using('gpr').create(assetid=asset_entry, hectares=hectares)             except ValidationError:               return Response({'error': 'hectares must be decimal value.'})             farmingasset = FarmingAssets.objects.using('gpr').filter(assetid=asset_entry, hectares=hectares).last()             for type_detail in asset_detail.get('type_details', []):               crop = type_detail.get('crop', '')               hectare = type_detail.get('hectare', '')               if crop != '' and hectare != '':                 try:                   h3code = ProductCodes.objects.using('gpr').get(h3code=crop)                 except:                   return Response({'error': 'Unable to find ProductCode with h3code' + str(crop)})                 try:                   farming = Farming.objects.using('gpr').create(assetid=farmingasset, h3code=h3code, annualyield=hectare)                   farming_details[asset_entry.assetid].append(farming.farmingid)                 except Exception as e:                   return Response({'error': e})               else:                 return Response({'error': 'crop with hectare is required.'})     i = 0     data = {}     for asset in assets:         if farming_details[asset.assetid]:           data[i] = {"assetid": asset.assetid, "assetbluenumber": asset.assetuniversalid, "farming_ids": farming_details[asset.assetid]}         else:           data[i] = {"assetid": asset.assetid, "assetbluenumber": asset.assetuniversalid}         i+=1     return Response(data) 

Asset Model

class Assets(models.Model):     assetid = models.CharField(db_column='AssetID', primary_key=True, max_length=255)  # Field name made lowercase.     assetname = models.CharField(db_column='AssetName', max_length=255, blank=True, null=True)  # Field name made lowercase.     locationid = models.ForeignKey('Locations', models.DO_NOTHING, db_column='LocationID')  # Field name made lowercase.     assetuniversalid = models.CharField(db_column='AssetBluenumber', unique=True, blank=True, null=True, max_length=255)  # Field name made lowercase.     assettype = models.CharField(db_column='AssetType', max_length=45, blank=True, null=True)  # Field name made lowercase.     assetinserted = models.DateTimeField(db_column='AssetInserted', blank=True, null=True, auto_now_add=True)  # Field name made lowercase.     assetupdated = models.DateTimeField(db_column='AssetUpdated', blank=True, null=True, auto_now=True)  # Field name made lowercase. 

3 Answers

Answers 1

You can make serializers, they have a very easy way to validate your data. As in your case all the fields seem to be required it becomes even easier.

Create a file on you api app like:

serializers.py

#Import Serializers lib from rest_framework import serializers  #Import your models here (You can put more than one serializer in one file) from assets.model import Assets  #Now make you serializer class class AssetsSerializer(serializers.ModelSerializer):     class Meta:         model = Profile         fields = '__all__'          #This last line will put all the fields on you serializer         #but you can also especify only some fields like:         #fields = ('assetid', 'assetname') 

On you view you can use your serializer(s) class to validate you data.

views.py

#Serializers from assets.serializers import AssetsSerializer  #Libraries you can use from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status  class AssetsViewSet(viewsets.ModelViewSet):     queryset = Assets.objects.using("gpr").all()      def create(self, request):         assets = []         farming_details = {}         #Set your serializer         serializer = AssetsSerializer(data=request.data)         if serializer.is_valid(): #MAGIC HAPPENS HERE             #... Here you do the routine you do when the data is valid             #You can use the serializer as an object of you Assets Model             #Save it             serializer.save()             return Response(serializer.data, status=status.HTTP_201_CREATED)         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

i took this all from the documentation. You can learn a lot doing the tutorial from the official site. I hope it helps.

Answers 2

You can do something like:

for param in ['bluenumberid', 'actorid', 'asset_details']:     if param not in request.data.keys():         raise Response({'error': '%s is required.' % param})   ...  for asset_detail in asset_details:     for param in ['location', ..., 'longitude']:         if param not in asset_detail.keys():             raise Response({'error': '%s is required.' % param})  

Answers 3

This is just a guide that you can follow for refactoring, of course many other things can be improved while performing this:

  • make a ModelSerializer for model Assets
  • AssetsModelSerializer should handle validation
  • within AssettsModelSerializer add any related ModelSerializer (like Locations) that has specific validation and representation
  • move the create method to AssetsModelSerializer and just handle there the model creation
  • AssetModelSerializer should provide a specific to_representation (if needed)
  • The AssetsViewSet is doing more then one thing as I see (especially the last part with FarmingAssets objects) can you split that logic in another view? or route?
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment