Is there a way how to dynamically generate a Django rest framework serializers?
Considering this:
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = models.Blog
fields = get_all_model_fields(models.Blog)
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Post
fields = get_all_model_fields(models.Post)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = get_all_model_fields(models.User)
I am wondering if something like following example could be possible:
from django.apps import apps
models = [model for model in apps.get_models()]
for model in models:
type(model.__name__+'Serializer',(serializers.ModelSerializer,),{
type("Meta",(),{
"model":model,
"fields": get_all_model_fields(model)
})
})
Or is there any other way how to generate DRF serializers?
Here's a function to build a ModelSerializer for a given Model (tested with Django 3.2, DRF 3.12.4 and Python 3.8):
from functools import lru_cache
from typing import Type
from django.db import models
from rest_framework import serializers
#lru_cache(maxsize=0)
def model_serializer(model: Type[models.Model]) -> Type[serializers.ModelSerializer]:
meta_class = types.new_class("Meta")
setattr(meta_class, "model", model)
setattr(meta_class, "fields", "__all__")
result = types.new_class(
model.__name__ + "Serializer", (serializers.ModelSerializer,), {}
)
setattr(result, "Meta", meta_class)
return result
If you are certain that you will call this function only once for each serializer, you can omit the #lru_cache to preserve some memory.
Example usage:
class MyModel(models.Model):
some = models.CharField(max_length=123)
other = models.IntegerField()
MyModelSerializer = model_serializer(MyModel)
my_serializer = MyModelSerializer({"some": "abc", "other": 1234})
my_serializer.is_valid(True)
To add the serializers for all your models to the current module's namespace:
from django.apps import apps
for model in apps.get_models():
serializer_type = model_serializer(model)
globals()[serializer_type.__name__] = serializer_type
Your approach will work - but for Django to find about these serializers you have to assign them to the module namespace.
In your code, you just call type - the serializer class is created and "thrown away" immediately. Even if the base Django class serializers.ModelSerializer keep a reference to it in a registry, you would not be able to import your serializers and make use of them.
All you have to do is to add them to the dictionary returned by globals(). Likewise, the namespace you create for the class also has to be a dictionary -since you are calling "type" but not actually assigning its name as "Meta", you create a set, not a dictionary, and your call to type will fail as it was expecting a dictionary.
So, I did not check if the code actually will work, but the idea, based on yours is:
from django.apps import apps
models = [model for model in apps.get_models()]
for model in models:
name = f"{model.__name__}Serializer"
globals()[name] = type(name,(serializers.ModelSerializer,),{
"Meta": type("Meta",(),{
"model":model,
"fields": get_all_model_fields(model)
})
})
del models, model, name
Related
I created a serializer and an API endpoint so I can retrieve some data from a Django DB in my React app but getting this error message:
AttributeError: 'ProgrammingChallengesView' object has no attribute 'get'
Here is my models.py:
#creating programming challenges
class ProgrammingChallenges(models.Model):
challenge_id = models.AutoField(primary_key=True)
challenge_name = models.CharField(max_length=200)
challenge_description = models.TextField()
challenge_expectations = models.TextField()
my serializer:
from accounts.models import ProgrammingChallenges
...
class ProgrammingChallengesView(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenges
fields = '__all__'
and my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView, name='programming_challenges'),
Thanks to the comments; I clearly didn't understand that a serializer only transforms my data to make it available through an API. I still had to create a view for my API's endpoint.
I opted to create a ReadOnlyModelView because I only want to GET data from this endpoint.
Here is what I wrote in my views:
class ProgrammingChallengesView(ReadOnlyModelViewSet):
serializer_class = ProgrammingChallengesSerializer
queryset = ProgrammingChallenges.objects.all()
#action(detail=False)
def get_list(self, request):
pass
and in my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView.as_view({'get':'list'}), name='programming_challenges'),
I think you shouldn't hurry read the docs again. You are trying to use serializers as views.
Models - are representation of db tables as class.
Serializer serializes the data to json.
View accepts the reqeust from client and returns a Response.
Code shoudld be:
models.py
class ProgrammingChallenge(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
expectations = models.TextField()
Your model name should be ProgrammingChallenge(singular) not ProgrammingChallenges(plural).
You should't add prefix challenge before all field names. Because we already know that the fields are in a Model called ProgrammingChallenge. And it is easy to access them like ProgrammingChallenge.name than ProgrammingChallenge.challenge_name
You don't have to add field id manually. Django model automatically adds id field as primary_key
serializer.py
from accounts.models import ProgrammingChallenge
...
class ProgrammingChallengeSerializer(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenge
fields = '__all__'
No problem in serialize.
Now, main problem is you don't have any view. You definetly read docs. You can use APIView, generic views or viewset. In this example i'm going to use ViewSet that handles CRUD operations built in.
viewsets.py
from rest_framework.viewsets import ModelViewSet
from .models import ProgrammingChallenge
from .serializers import ProgrammingChallengSerializer
class ProgrammingChallengViewSet(ModelViewSet):
queryset = ProgrammingChallenge.objects.all()
serializer_class = ProgrammingChallengeSerializer
urls.py
from rest_framework.routers import SimpleRouter
from .viewsets import ProgrammingChallenge
router = SimpleRouter()
router.register('challengr', ProgrammingChallengeViewSet)
urlpatterns = router.urls
Another advantage of using viewset, it also generate all endpoint for it's CRUD methods automatically via routes.
It should help you to start your first project.
AGAIN, READ THE DOCS!
I have 2 different application in python. I want to import model and use it to make serializer function in another app. Here is my code:
from django.contrib.auth import models as auth_models
from . import models as client_models
from crm.models import models as crm_models
from rest_framework import serializers
class Capability(serializers.ModelSerializer):
class Meta:
model = crm_models.Capability
fields = ["id", "name"]
class Client(serializers.ModelSerializer):
industry = Industry(read_only=True)
capability = Capability(read_only=True)
class Meta:
model = client_models.Client
fields = [
"id",
"company",
"entity",
"account_status",
"capability"]
Here I am getting error for,
in Meta
model = crm_models.Capability
AttributeError: module 'django.db.models' has no attribute 'Capability'
Without seeing the file structure I'd take a guess that you want to change your import to:
from crm import models as crm_models
as I think you are looking for models in models currently.
Try to use this :-
from crm.models import Capability
class Capability(serializers.ModelSerializer):
class Meta:
model = Capability
fields = ["id", "name"]
I am looking for a way to get a serializer from a model class. This is so that I can easily serialize model data, without having to harcode the serializer name and I figured something like this would do:
#mymodels.py
from django.db import models
import myserializers
class Model(models.Model):
name = models.CharField(max_length=50)
serializer = serializers.Serializer
#myserializers.py
from rest_framework import serializers
import mymodels
class Serializer(serializers.ModelSerializer):
class Meta:
model = mymodels.Model
fields = ('field1', 'field2')
The model is for an intermediary relationship, and I want a function that will act something like this:
def serialize(to_serialize):
return type(to_serialize).serializer(to_serialize).data
But this raises a AttributeErrordue to import errors. Am I going about this the completely wrong way? Is there an easier way of doing this, or is there a way to make this work somewhat like the above?
Thanks in advance.
Try this way may be work for you:
class Model(models.Model):
name = models.CharField(max_length=50)
serializer = serializers.Serializer
class Meta:
serializer_class = SerailizerClassName
In this code serializer set in model
now use the serializer in method
def serialize(model_object):
return model_object.__class__.Meta.serializer_class(model_object).data
I'm using drf_mongoengine for the first time and I'm having problems setting up the models. I want the documents to be initialized like this:
{
"name" : "new_name",
"metadata": {
"total_efficiency": 0.0,
"eff_vs_layer_thickness":{
"x":[],
"y":[]
}
}
}
The documents are created without the "metadata" field. What Am I missing?
Models:
class Detector(Document):
name = fields.StringField(null=True)
metadata = fields.EmbeddedDocumentField(Metadata, null=False)
class Metadata(EmbeddedDocument):
eff_vs_layer = fields.EmbeddedDocumentField(Plot)
total_efficiency = fields.DecimalField(null=True, default=0)
class Plot(EmbeddedDocument):
x = fields.ListField(fields.FloatField(null=True), default=[])
y = fields.ListField(fields.FloatField(null=True), default=[])
Serializer:
class DetectorSerializer(mongoserializers.DocumentSerializer):
class Meta:
model = Detector
fields = '__all__'
class MetadataSerializer(mongoserializers.EmbeddedDocumentSerializer):
class Meta:
model = Metadata
fields = '__all__'
View:
class DetectorViewSet(viewsets.ModelViewSet, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
'''
Contains information about inputs/outputs of a single program
that may be used in Universe workflows.
'''
lookup_field = 'id'
serializer_class = DetectorSerializer
#alvcarmona, welcome to DRF-ME. You're generally doing everything right.
Just a couple of things: you don't need MetadataSerializer, as it will be created automatically inside DetectorSerializer.
You also shouldn't mix mixins.UpdateModelMixin and mixins.DestroyModelMixin into a full viewset (viewsets.ModelViewSet), instead, mix them into rest_framework_mongoengine.generics.GenericApiView (like here: https://github.com/umutbozkurt/django-rest-framework-mongoengine/blob/master/rest_framework_mongoengine/generics.py#L16).
Other than that, I think, you're doing everything right. If you can show me your project on github, I could help more.
UPDATE: to mix mixins into generic view, do it as we do here e.g.:
class PostPutViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
GenericViewSet):
""" Adaptation of DRF ModelViewSet """
pass
I am trying to use save() function in my model to auto populate the field via use of the api.. here is the code(models.py:
from .views import savee
class SaveRushh(models.Model):
keyword = models.ForeignKey(KW)
u1 = models.URLField()
u2 = models.URLField()
def save():
savee(keyword)
And the function itself:
from .serializer import RushSerializer
def savee(keyword):
nov = {'u1': 'https://en.wikipedia.org/wiki/Yellow', 'u2': 'https://en.wikipedia.org/wiki/White_color'}
nov['keyword'] = keyword
serializer = RushSerializer(data=nov)
if serializer.is_valid():
serializer.save()
And here is serializer code:
from rest_framework import serializers
from .models import SaveRushh
class RushSerializer(serializers.ModelSerializer):
# key = serializers.StringRelatedField(many=True)
class Meta:
model = SaveRushh
fields = ('keyword', 'u1', 'u2')
When I try to run it it gives this error:
File ".../serializer.py", line 2, in
from .models import SaveRushh ImportError: cannot import name SaveRushh
I think I understand that what I've done is something like circular import(SaveRushh already exists in models)
I just can't think of a good way to get around it.
To make it more clear, I want save() function inside my model to be ran every time model is created/updated, calling another function that uses rest api to populate model with data.