Search code examples
python-3.xdjangodjango-rest-frameworkdjongo

Djongo Models - How to get rid of : Object of type AssertionError is not JSON serializable


I'm having a weird problem in my django Restfull Application ! i'm using Djongo as db engine (MongoDB)

the problem seems to be happening in /usr/local/lib/python3.8/dist-packages/rest_framework/utils/encoders.py which sometimes say create() function returned null object or returned AssertionError as Object which is not Serializable at the end !

Model :

class Product(models.Model):
   """
   Product Model
   """

   _id         = models.ObjectIdField()
   name        = models.CharField(max_length=MAX_PROD_NAME_LEN)
   sku         = models.CharField(max_length=MAX_PROD_SKU_LEN , unique=True)
   category    = models.CharField(max_length=MAX_PROD_CAT_LEN)
   desc        = models.TextField(default="")
   agent_desc  = models.TextField(default="")
   urls        = models.JSONField(blank=True , null=True)
   productType = models.CharField(max_length=MAX_PROD_TYPE_LEN)
   variants    = models.JSONField(default={
       # "op1":models.CharField(max_length=MAX_PROD_OPTION_LEN),
       # "op2":models.CharField(max_length=MAX_PROD_OPTION_LEN),
       # "op3":models.CharField(max_length=MAX_PROD_OPTION_LEN),
       # "quantity":models.IntegerField(default=1),
       # "price":models.DecimalField(max_digits=10, decimal_places=2),
   })

   options     = models.JSONField(default={})
   accessories = models.JSONField(default={
       # "name"  : None,
       # "attachements":[],
       # "quant" : None,
       # "desc"  : None
   })
   created_at  = models.DateTimeField(auto_now_add=True , null=False)
   updated_at  = models.DateTimeField(auto_now=True, null=True)
   deleted_at  = models.DateTimeField(default=None)
   is_deleted  = models.BooleanField(default=False)

   objects     = models.DjongoManager() # built-in Model's objects Alike !
   
   
   def __str__(self):
       return self._id

View :


class ProductView(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer


    def list(self, request):
        queryset = Product.objects.all()
        serializer = ProductSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        print(f"Retrieving infos of {pk}")
        queryset = Product.objects.all()
        oid = None
        try:
            oid = ObjectId(pk)
        except Exception:
            return Response("Product not found !")
        
        product = get_object_or_404(queryset, _id=oid)
        serializer = ProductSerializer(product)
        return Response(serializer.data)

    def create(self, request):

        try:
            print(f'{type(request.data)} -- {dict(request.data)}')

            serializer = ProductSerializer(data=dict(request.data))
            prod = serializer.save()
            # if serializer.is_valid(raise_exception=True):
            return Response({'status':'success' , 'object':prod._id})
            # else:
            #     return(Response({'status':'failed' , 'error':'Failed Creating Product'}))
        except Exception as ex:
            return(Response({'status':'failed' , 'error':ex}))

    # def create(self, request, *args, **kwargs):
    #     response = super().create(request, *args, **kwargs)
    #     instance = response.data
    #     return Response({'status': 'success', 'pk': instance['pk']})
        

Serializer :

class ProductSerializer(ModelSerializer):
    """
    Product Serialiazer !
    """
    class Meta(object):
        model   = Product
        fields  = '__all__'

made this external script to test if the post request goes well

Note the the auth token goes well and everything smooth , only the creation does not work Test_Post.py :

import requests as req
import json
from sys import argv
from datetime import datetime as dtm

link = 'http://localhost:8000/api/token/'
r=req.post(link , {'username':'root', 'password':'root'})
#print(r.text)
auth_access = json.loads(r.text)['access']
pyload = {
    "name" : "prod2",
    "sku" : f"asaasaku2{dtm.now()}",
    "category" : "casaat2",
    "desc" : "desasc2",
    "agent_desc" : "asaagentDesc2",
    "productType" : "typdef2f",
#    "variants" : {
#        "op1" : "o1",
#        "op2" : "o2",
#        "op3" : "o3",
#        "quantity" : 10.0,
#        "price" : 500.0
#    },
#    "accessories" : [ 
#        "a1", 
#        "a2"
#    ]
}

r = req.post(f'http://localhost:8000{argv[1]}', json=pyload, headers={'content-type':'application/json','Authorization':'Bearer '+str(auth_access)})

print(r.text)

error :

Internal Server Error: /prods/
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/lib/python3.8/site-packages/django/core/handlers/base.py", line 145, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/lib/python3.8/site-packages/django/core/handlers/base.py", line 143, in _get_response
    response = response.render()
  File "/usr/lib/python3.8/site-packages/django/template/response.py", line 105, in render
    self.content = self.rendered_content
  File "/usr/local/lib/python3.8/dist-packages/rest_framework/response.py", line 70, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
  File "/usr/local/lib/python3.8/dist-packages/rest_framework/renderers.py", line 100, in render
    ret = json.dumps(
  File "/usr/local/lib/python3.8/dist-packages/rest_framework/utils/json.py", line 25, in dumps
    return json.dumps(*args, **kwargs)
  File "/usr/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/usr/lib/python3.8/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.8/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/lib/python3.8/dist-packages/rest_framework/utils/encoders.py", line 68, in default
    return super().default(obj)
  File "/usr/lib/python3.8/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type AssertionError is not JSON serializable


Solution

  • The Problem

    This is your problematic line:

    return(Response({'status':'failed' , 'error':ex}))
    

    You are passing the actual execption object (ex) as part of the response, which is in turn serialised to JSON. But there is no built in methods for doing this.

    The solution

    Instead of returning the whole exception, you should return just the message. Something like this:

    return(Response({'status':'failed' , 'error':str(ex)}))
    

    Also...

    It's bad practice to do this:

    except Exception as ex:
    

    This will capture all possible errors, which you don't want. Some things should fail at different points. Django has it's own exception handling built in, so nothing is going to completely crash your server. If something is going wrong which you don't expect you need to know about this, and let the error bubble all the way up. Not capture it like this. It makes debugging a lot more difficult.

    It's much better to capture a specific error. An additional advantage of this is you can write your own message, which will make more sense to the end user. e.g:

    except AssertionError:
        return(Response(
             {'status':'failed' , 
              'error': 'Your own message here'
             })
        )