How to write a generic CRUD service on django using rest framework? - django-rest-framework

I am new to django and faced to several problems trying to write a simple service.
What am I trying to do?
I intend to write a generic crud service for my models using rest-framework library.
I don't want to write serializers and views to all my models and trying to optimize code and learn some useful stuff.
My model
Let's imagine I have an abstract BaseBusinessObject
class BaseBusinessObject(models.Model):
CreatedAt = models.DateField()
UpdatedAt = models.DateField()
class Meta:
abstract = True
I also have a plenty of concrete classes, which are inherited from base one:
class Product(BaseBusinessObject):
Description: models.TextField()
Type: models.CharField()
....
class Company(BaseBusinessObject):
Title: models.CharField()
....
class Person(BaseBusinessObject):
Name: models.CharField()
and so on
What I want
I already figured out, that with rest-framework I can create serializers and views, then register router for url .../Product, .../Company, .../Person. But what if I have 1000 classes? This is boring
A. How can I dynamically specified url's for child objects? I don't want to hardcode methods, I am looking for solution...something like this:
.../api/Entities/ClassName
B. How can I then use my dynamically created urls in django-rest-framework?
router.register('persons', PersonViewSet)
How can write it in more generic way?
router.register('<ClassName>', <GenericViewSet>)
C. Using DRF I can create my viewset for each concrete class in my model:
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
But as I said I have a lot of classes. How can I write it in more generic way?
I tried to create a view set for abstract class, but there are some trouble when querying an abstract object.
Is it possible to create such service for an abstract class and then all its child simply(or not simply) inherit CRUD methods?
Maybe I should try to write a factory for serializers and viewsets?
What possible solutions could I implement for solving my problem?

After 2 days of walking around I finally find my solution. May be someone else will face the some problem, so I trying to explain what I had already done.
First, I create a "base" application inside my django project and add it to settings.py. Then I create an abstract class in models.py:
class BaseCrudEntity(models.Model):
pass
class Meta:
abstract = True
I want to write a generic service for CRUD operations for all "business" classes.
The problem is that I don't want to write serializers and views for them - I want to create them "on fly", dynamically. I decided to use django rest framework as well, because I am not intended to create a bycicle again.
I decided to inherit all my "business" classes from that abstract one and write a service for all possible "families"
So I have to create a fabric which is responsible for VeiwSet creation.
Here is my view.py:
class BaseCrudViewSetFabric():
#classmethod
def CreateViewSet(self, _context):
classname = _context.__name__ + 'ViewSet'
return type(classname, (viewsets.ModelViewSet,), {
'queryset':_context.objects.all(),
'serializer_class':BaseCrudSerializerFabric.CreateSrializer(_context)
})
pass
here _context - variable which describes concrete class of my model.
as you can see this function creates a concrete ViewSet based on my context. Inside it a Serializers fabric is called.
Here the code of my serializers.py:
class BaseCrudSerializerFabric():
#classmethod
def CreateSrializer(self, _context):
classname = _context.__name__
_Meta = type('Meta', (), {'model':_context,'fields':'__all__'})
_crudserializer = type(
classname,
(serializers.ModelSerializer,),
{'Meta': _Meta}
)
return _crudserializer
Moreover, I have to write a service for dynamically routing - I don't wanna hardcode my urls.
Here the example ursl.py from core project:
from base.urls import router
url(r'^api/v1/', include(router.urls))
and from base/urls.py:
from rest_framework.routers import DefaultRouter, SimpleRouter
from base.models.BaseCrudEntity import BaseCrudEntity
from base.views.basecrud_view import BaseCrudViewSetFabric
class CustomRouter(SimpleRouter):
def RoutRegister(self):
childs = getChilds(BaseCrudEntity)
#print(childs)
for ch in childs:
if (ch._meta.abstract == False):
#print(ch.__name__)
prefix = ch.__name__
self.register(prefix, BaseCrudViewSetFabric.CreateViewSet(ch))
return(self)
pass
router = CustomRouter()
router.RoutRegister()
Finally I simply create some concrete models:
from django.db import models
from base.models.BaseCrudEntity import BaseCrudEntity
class Person(BaseCrudEntity):
Name = models.CharField(max_length = 255)
Surname = models.CharField(max_length = 255)
Patronymic = models.CharField(max_length = 255, null = True)
DateOfBirth = models.DateField(null = True)
#slug = models.SlugField(default = 'hui', editable = False)
def __str__(self):
return "{} {} {}".format (self.Surname, self.Name, self.Patronymic)
and thats all.
When application starts it register a route for http://127.0.0.1:8000/api/v1/Person and creates serializers and viewsets so all CRUD operations provided by Django- Rest Framework will be provided as well.

I would suggest using query parameters instead of path parameters, then you just register one URL and process the various elements of the request and route it correctly server-side. When do I use path params vs. query params in a RESTful API?

Related

Serialize available choices and mark selected

I am new to REST and django-rest-framework. I want to get list of available ManyToMany choices along with some way to know which ones are currently selected.
I have model like this:
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
Serializers.
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['pk', 'name',]
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
class Meta:
model = PGroup
fields = [....'permissions']
Looking at Browseable API, with this setup I get 'permissions: []'(empty list) for generics.createAPIView and get the associated 'permissions[....]'(non-empty list) for generics.RetrieveUpdateAPIView.
I want a list of available permissions on both API views and also want to know which permissions are already selected for Update API view.
Can anyone please help.
Thanks
There are 2 ways to get the list of choices.
Using the SerializerMethodField,
from rest_framework import serializers
from .models import Permission
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = serializers.SerializerMethodField()
def get_all_available_permissions(self, obj):
return Permission.objects.all()
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
or using source, we can define a custom method on the model and point the serializer to use it using the source argument.
### models.py
class PGroup(models.Model):
.
permissions = models.ManyToManyField(
Permission, related_name="group_permissions", help_text=_('Select permissions for this group.')
)
def all_permissions(self):
return Permission.objects.all()
### serializers.py
class PGroupSerializer(serializers.ModelSerializer):
permissions = PermissionSerializer(many=True)
all_available_permissions = PermissionSerializer(many=True, read_only=True, source="all_permissions")
class Meta:
model = PGroup
fields = ['permissions', "all_available_permissions"]
2nd option is much better, IMO.
Note: you may not always want to send a full list of choices as that could get really slow overtime when u have hundreds or thousands of objects.

Django REST Framework: "NoneType object is not iterable" error when trying to use serializer.data construct from within Serializer Method Field?

I am using a model that consists of many fields. There is one field that is a property, and it returns an instance of a model. Something like the following:
class A(Model):
#property
def last_obj(self):
# Returns an object
The issue I'm having is that this property can return 2 different Model types. It can either return an object of type one, or an object of type two. This creates complications in the serializer. I have a serializer that consists of nested serializers. The two objects are similar enough that one serializer can be used over the other, but then the fields unique to them are not serialized.
class A_Serializer(Serializer):
class SerializerOne(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.one
exclude = ('id')
base_name = 'one'
class SerializerTwo(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.two
exclude = ('id')
base_name = 'two'
last_obj = SerializerOne() #This works, but not viable because of what I stated above
So my solution to be able to dynamically call the correct serializer, was to conditionally serialize the property within a serializer method field:
class A_Serializer(Serializer):
class SerializerOne(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.one
exclude = ('id')
base_name = 'one'
class SerializerTwo(CustomSerializer):
#Serializes certain fields in custom manner
class Meta:
model = models.two
exclude = ('id')
base_name = 'two'
def get_last_obj(self, instance):
if (isinstance(instance.last_obj, models.one)):
return self.SerializerOne(instance.last_obj).data
else:
return self.SerializerTwo(instance.last_obj).data
last_obj = SerializerMethodField() #Does not work
However, this solution creates the error "NoneType Object is not iterable" and it happens at
super(ReturnDict, self).__init__(*args, **kwargs) in rest_framework/utils/serializers_helpers.py in init which causes the error at return ReturnDict(ret, serializer=self) in rest_framework/serializers.py in data
I do not understand why calling a nested serializer like obj = Serializer() works, but calling the serializer explicitly like obj = Serializer(instance).data does not work in this situation. Can anyone figure out what I have been doing wrong? Thank you.
I have found out from here that when working with hyperlinked relations (which in my case was the CustomSerializer that SerializerOne and SerializerTwo were inheriting from), you must pass the request object through context. The reason why obj = Serializer() works, but obj = Serializer(instance).data does not work is that in the former, the request object is automatically added through context through DRF. While in the latter, it is being explicitly called so you must pass context with the request object manually. So for me to get it working, I did:
return self.SerializerOne(instance.last_obj, context={'request': self.context['request']}).data
inside the serializer method field.

How to use a custom id with Graphene and Relay?

I've implemented graphql and I'm migrating to relay. I already have a uuid for every table and that is named 'id'. And my application I found this github thread that talks about possibly changing the spec but it feels like a rabbit hole.
Is there a simple way that I can use my own custom id with relay?
If you've already implemented a default relay endpoint then you should have some
TableNameNode classes that have a Meta nested class, and a seperate Query
class.
class ExampleTableNameNode(DjangoObjectType):
class Meta:
model = ExampleTableName
interface = (relay.Node,)
class Query(object):
example_table_name = relay.Node.Field(ExampleTableNameNode)
all_example_table_names = DjangoFilterConnectionField(ExampleTableNameNode)
def resolve_example_table_name(self, info, **kwargs):
pass
def resolve_all_example_table_names(self, info, **kwargs):
pass
The interface = (relay.Node,) is what defines:
How the ids are being generated
How they are used to fetch data
If we create a relay.Node subclass that redefines these two features then we can use our custom ids.
class CustomNode(relay.Node):
class Meta:
name = 'Node'
#staticmethod
def to_global_id(type, id):
#returns a non-encoded ID
return id
#staticmethod
def get_node_from_global_id(info, global_id, only_type=None):
model = getattr(Query,info.field_name).field_type._meta.model
return model.objects.get(id=global_id)
Here we implemented two functions, to_global_id, and get_node_from_global_id.
The line model = ... is a bit of magic to go from the graphql query table name
to the actual model. If that doesn't work you'll just need to make a dictionary
to go from something like example_table_name to the actual ExampleTableName
django model.
Once you do that you'll have to replace the two references to relay.Node with
CustomNode like so.
class ExampleTableNameNode(DjangoObjectType):
class Meta:
model = ExampleTableName
interface = (CustomNode,)
class Query(object):
example_table_name = CustomNode.Field(ExampleTableNameNode)
all_example_table_names = DjangoFilterConnectionField(ExampleTableNameNode)
def resolve_example_table_name(self, info, **kwargs):
pass
def resolve_all_example_table_names(self, info, **kwargs):
pass
The answer is in the graphene docs. I read them when I was implementing
graphene and relay but there is so much to learn at once that it's easy to read
through custom node section and not remember later that you need to do a custom
node solution.

Writable many-to-many field in Django Rest Framework tries to save new child objects?

Edit: This is using django rest framework 2.3
I have a model structure that has 3 relationship "levels", one of which is many to many.
class Shipment(models.Model):
stuff...
class ShipmentItem(models.Model):
shipment = models.ForeignKey(Shipment)
assets = models.ManyToMany(ShipmentAsset)
class ShipmentAsset(models.Model)
serial_number = models.CharField(unique=True)
Using Django rest framework I want to be able to post to the "Shipment" endpoint with a payload that contains the ShipmentItems for the Shipment, and the ShipmentAssets for the ShipmentItems ideally in one request.
The serializers are as follows..
class ShipmentAssetSerializer(serializers.ModelSerializer):
class Meta:
model = ShipmentAsset
field = ('id', 'serial_number', )
class ShipmentItemSerializer(serializers.ModelSerializer):
assets = ShipmentAssetSerializer(
many=True, required=False, allow_add_remove=True,
)
class Meta:
model = ShipmentItem
fields = ('id', 'assets', )
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = (
'id',
)
The shipmentItem/Shipment relationship seems to work when I post to it with the assets part disabled, but when I try to post assets in the payload, It appears to be trying to create NEW assets with the posted data (I get an error regarding the unique constraint on the serial number) rather than creating a new many-to-many table object. Any idea what I'm doing wrong?
Edit: Important clarification, I'm using Django Rest Framework 2.3.13

Foreign key field disappears in swagger docs after adding depth attribute in Serializer

Whenever I define the depth attribute, the foreign key field from swagger docs in POST section disappears. That seems strange because I required depth = 1 when I want related data in my GET request. So I can not remove this in order to get this related field parameter in the POST section.
Here is the case.
Model:
from django.db import models
from django.conf import settings
# Create your models here.
User = settings.AUTH_USER_MODEL
class Todo(models.Model):
user = models.ForeignKey(User)
title = models.CharField("Title", max_length=255)
completed = models.BooleanField("Completed")
Serializer without depth =1.
from rest_framework import serializers
from models import Todo
class TodoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Todo
Swagger output:
Now If I add depth = 1 than Swagger does not display related field.
Let me know if anyone has any clue about this.
Thanks :)
Finally after digging into this, I come up with solution by which we can avoid this issue and achieve the expected solution.
So the solution is "Instead of using depth = 1 attribute we can using related serializer instance it self where it works similar to depth functionality."
Here is tested solution
Model:
from django.db import models
from django.conf import settings
User = settings.AUTH_USER_MODEL
class Todo(models.Model):
user = models.ForeignKey(User)
title = models.CharField("Title", max_length=255)
completed = models.BooleanField("Completed")
Serializer
from rest_framework import serializers
from django.conf import settings
from models import Todo
User = settings.AUTH_USER_MODEL
class UserSerializer(serializers.HyperlinkedSerializer):
class Meta:
model = User
class TodoSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Todo
fields = ('user', 'title', 'completed')
Swagger Output:
This solution is kind of different approach in order to achieve the required functionality, But still I am expecting an official solution from django-rest-swagger team, Even I have posted the same query on django-rest-swagger github repo here.
One solution is to just don't use depth and override to_representation method of serializer:
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
def to_representation(self, instance):
r = super(TodoSerializer, self).to_representation(instance)
r.update({'user': UserSerializer().to_representation(instance.user)})
return r
This way, in post everything will be as it was, and in get when return json of todo then to_representation will be called and will add user to json data.
You need to update your serialzer as follows
class TodoSerializer(serializers.HyperlinkedModelSerializer):
creator = serializers.RelatedField(queryset=User.objects.all())
class Meta:
model = Todo
fields = ("name", "task", "creator")
depth = 1
you need to mentions fields and RelatedField in your serializers

Resources