I have a Pydantic class as below. Fields id and partNumber both have their alias defined in the config. The issue is the attribute field is a dictionary which has fields in snake_case. I would like to create alias for those fields in camelCase. How can I create an alias for a dictionary field?
class LibraryPartModel(BaseModel):
id: Optional[int]
partNumber: Optional[str]
attribute: Optional[dict]
class Config:
allow_population_by_field_name = True
fields = {
"id": {"alias": "id"},
"partNumber": {"alias": "part_number"},
"attribute": {"alias": "attribute"}
}
EDIT: I wanted to add that the attribute dictionary will have different fields. I won't be able to create another class and hardcode fields/aliases as the fields within attribute will be different every time.
Related
Context:
models.py
Let's say I have a Generic Django Model class called "Base" to group all other classes that extend it:
class Base(models.Model):
name = models.CharField(...)
belongs_to = models.ForeignKey(
'self', on_delete=models.PROTECT, blank=True, null=True
)
And the class "Company", that extends the "Base":
class Company(Base)
pass # It just extends the Base Attributes.
The class "Manager", also extends "Base" but it has a role attribute:
class Manager(Base)
role = models.ForeignKey(Roles, on_delete=models.CASCADE ...)
And the class "Worker", that also extends "Base" but it has a job attribute:
class Worker(Base):
job = models.ForeignKey(Jobs, on_delete=models.CASCADE ...)
Implementation
Here's an example of how they can be instantiated:
Company.objects.create(name='Company1',
belongs_to=None) # This will also create a Base Object with the ID 1.
Manager.objects.create(name='Manager1',
belongs_to=1 [[company]],
role=1) # This will create a Manager object and relate it to the Company.
# (And also create a Company object with the ID 2, that don't make much sense, but is only an abstract example)
Worker.objects.create(name='Worker1',
belongs_to=2 [[manager]], job=1) # In this case, I'm saying that the Worker is grouped under the Manager Object.
The Idea is that, the Worker can also not have a manager and be directly related under a Company, like:
(...)
Worker.objects.create(name='Worker34',
belongs_to=1 [[company]],
job=1) # In this case, I'm saying that the "Worker" is grouped under the "Company" Object.
Django Rest and Question Wrap-up:
Now to the DJANGO REST in to my question:
I'm using serializer.ModelSerializer (since I don't have much experience with Software Architecture, and I'm trying the keep it as simple and efficient as possible) with the 'Meta' depth to have Nested Objects:
serializer.py
class WorkerSerializer(serializers.ModelSerializer):
class Meta:
model = Worker
fields = '__all__'
depth = 3
But since the field belongs_to is an attribute of the Base it returns only the information Containing in Base, like this:
{
"id": 3,
"name": "Worker1,
"belongs_to": {
"id": 2,
"nome": "Manager1",
****this is where I would like to have the aditional Manager Object info."
"belongs_to": {
"id": 1,
"nome": "Company1",
}
},
},
In this example, I would need the Manager 'roles' attribute from the Roles Table (Omitted in this example), to also retrieve information from there.
Is there any way I can point to the Serializer that It should First try to see if the 'belongs' to point to a higher chain object or since it is an attribute of base, It'll only return the Base attributes serialized
Is it possible, is it viable and how do I do it?
You can extend the field reference in serializers with another serializers. Like:
class ManagerSerializer(serializers.ModelSerializer):
class Meta:
model = Manager
fields = '__all__'
class WorkerSerializer(serializers.ModelSerializer):
belongs_to = ManagerSerializer()
class Meta:
model = Worker
fields = '__all__'
That way you can return more details about a field in a ForeingKey relationship
I use Fast API to create a web service.
There are following sqlAlchemy models:
class User(Base):
__tablename__ = 'user'
account_name = Column(String, primary_key=True, index=True, unique=True)
email = Column(String, unique=True, index=True, nullable=False)
roles = relationship("UserRole", back_populates="users", lazy=False, uselist=True)
class UserRole(Base):
__tablename__ = 'user_role'
__table_args__ = (UniqueConstraint('role_name', 'user_name', name='user_role_uc'),)
role_name = Column(String, ForeignKey('role.name'), primary_key=True)
user_name = Column(String, ForeignKey('user.account_name'), primary_key=True)
users = relationship("User", back_populates="roles")
Pydantic schemas are below:
class UserRole(BaseModel):
role_name: str
class Config:
orm_mode = True
class UserBase(BaseModel):
account_name: str
email: EmailStr
roles: List[UserRole] = []
class Config:
orm_mode = True
What I have now is:
{
"account_name": "Test.Test",
"email": "Test.Test#test.com",
"roles": [
{
"role_name": "all:admin"
},
{
"role_name": "all:read"
}
]
}
What I want to achieve is to get user from api in following structure:
{
"account_name": "Test.Test",
"email": "Test.Test#test.com",
"roles": [
"all:admin",
"all:read"
]
}
Is that possible? How should I change schemas to get this?
If you are okay
with handling the how to "get user from api" problem statement
by modifying the fastapi path definition, see below.
Can you change the response model used by the fastapi path definition
in order to handle the desired output format?
Example pydantic response model definition:
class UserResponse(BaseModel):
account_name: str
email: EmailStr
roles: List[str]
Example sqlalchemy query + serialization function:
def get_user_response(user_id) -> UserResponse:
user = User.query.get(user_id)
user_roles = UserRole.query.filter(user=user_id).all()
role_names = [r.role_name for r in user_roles]
response = UserResponse(
account_name=user.account_name,
email=user.email,
roles=role_names
}
return response
Example fastapi path definition:
#app.get("/users/{user_id}", response_model=UserResponse)
async def read_item(user_id):
return get_user_response(user_id)
Considerations:
I'm using a user_id for the user queries but this can be replaced with whatever you end up using as your primary key for that table.
The UserResponse response model is very similar to UserBase (you could potentially
subclass UserBase instead of model to avoid the redefinition of
account_name and email, with the tradeoff of having to override the
class' Config).
There may be a way to override the serialization format of the UserBase
sqlalchemy model object that gets automatically serialized when you query the model from the database
and allows you to eliminate or reduce the code in the get_user_response() function in the example definition above.
As UserRole is a class, it is represented as an object (using a dictionary). If you want to represent it as a list of strings you'll have to transform the data (and change your Pydantic model's field declaration). There's several approaches to this, but the pydantic model documentation is a good place to start. Mind that the ORM model serves as the data representation layer and the pydantic model is the validation (and perhaps serialization) layer, so there's a lot of places where you could 'hook in'.
I have two models Category and Products, and a classic one to many relatoin between them. Each product belongs to a category.
I am using Django-Rest-Framework.
I am using ModelViewSet as a ViewSet and ModelSerializer as a Serializer.
I created the ProductViewSet which has the full CRUD opertions.
What I am trying to achieve is to include the relation in the READ operations ( list , retrieve ). The response should look something like that.
{
"id" : 2,
"name" : "foo product" ,
"category" : {
"id" : 4,
"name" : "foo relation"
}
}
So I set the depth field in Meta equal to 1.
class ProductSerializer(ModelSerializer):
class Meta:
model = Product
exclude = ('createdAt',)
depth = 1
What makes the category read only.But I want it to be writable in the other actions (POST , PUT).
I know I can solve the problem using two serializes but this is a repetitive problem and I don't want to write two serializes for each model I have.
I tried to add category_id field in the serializer but I had to override the create method to make it work correctly.
And again I am trying to write less code. because I would end up overriding all of the app serializers.
class ProductSerializer(ModelSerializer):
category_id = PrimaryKeyRelatedField(queryset=Category.objects.all())
class Meta:
model = Product
exclude = ('createdAt',)
depth = 1
def create(self, validated_data):
category_id = validated_data.pop('category_id').id
validated_data['category_id'] = category_id
return super().create(validated_data)
This is my first Django framework and I am trying to make it as neat as possible.
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'm having trouble loading an external data fixture for a model that uses a ContentType foreign key.
I'm using a manager in the models, like the docs say. Unfortunately, although the docs talk about the importance of a get_by_natural_key method on a ContentType foreign key, it then launches into a different example instead. I'm having trouble figuring out just what the manager would look like. My best guess is to use get_by_natural_key again, and assign app_label and model lookups, but I could be way off.
# models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
class InlineTypeManager(models.Manager):
def get_by_natural_key(self, title, app_label, model):
return self.get(title=title, content_type=ContentType.objects.get_by_natural_key(app_label=content_type__name, model=content_type__id))
class InlineType(models.Model):
title = models.CharField(_("title"), max_length=255)
content_type = models.ForeignKey(ContentType, limit_choices_to={"model__in": ("Link", "Document", "Image", "Audio", "Video", "MediaSet", "Flash", "Testimonial")})
objects = InlineTypeManager()
class Meta:
ordering = ["title"]
verbose_name = _("inline type")
verbose_name_plural = _("inline types")
def __unicode__(self):
return u"%s" % (self.title)
https://docs.djangoproject.com/en/dev/topics/serialization/#natural-keys
My initial_data.json:
[
{
"model": "inlines.inlinetype",
"pk": 1,
"fields": {
"title": "Image",
"content_type": "image"
}
}, {
"model": "inlines.inlinetype",
"pk": 2,
"fields": {
"title": "Video",
"content_type": "video"
}
}
]
When I loaddata my JSON, I receive the error:
DeserializationError: [u"'image' value must be an integer."]
The point of get_by_natural_key is to load non-integer fields in a "human-friendly" lookup because hard-coded IDs in the JSON is a bad idea because of its unpredictability, so I'm guessing my manager is failing. Or should I use get_for_model()/get_for_models()?
natural key in Django is
The default serialization strategy for foreign keys and many-to-many relations is to serialize the value of the primary key(s) of the objects in the relation.
You don't need to implement methods such as natural_key and get_by_natural_key in Manager for those models which do not occur as ForeignKey/ManyToManyField in targets to dump. So you could remove InlineTypeManager() lines.
Also, the values of content_type field inside dumped initial_data.json are incorrect. Django only treats a list as natural key, a string like "image" is still treated as surrogate key and will fail because it cannot be coerced to int successfully. Correct ContentType dump looks like
from django.contrib.contenttypes.models import ContentType
from django.utils import simplejson
>>> simplejson.dumps(ContentType.objects.get(model='user').natural_key())
'["auth", "user"]'