a third-party application I am using is storing json in a textfield.
I would like to serialize this data to json, and I only need to be able to read from this serializer, not write to it. I don't want to have to manipulate the data on the frontend, so I want it to come out at clean json from my api.
class SomeSerializer(serializers.ModelSerializer):
details = serializers.CharField()
class Meta:
model = SomeModel
fields = ( 'id', 'details')
right now this is returning:
[{"id":"someID",
"details":"{\"address\": {\"city\": null}"}"}]
I can't figure out how to use json.loads in a serializer, which would seem the cleanest option.
You can use SerializerMethodField.
import json
from rest_framework import serializers
class SomeSerializer(serializers.ModelSerializer):
details = serializers.SerializerMethodField()
class Meta:
model = SomeModel
fields = ('id', 'details')
def get_details(self, obj):
return json.loads(obj.details)
Note that SerializerMethodField is read_only, cannot for write.
I am currently using UUID in my PostgreSQL database, therefore I am also using PrimaryKeyRelatedField() with some parameters in order to avoid problems when encoding to JSON the UUID field.
My serializer field looks like:
id = serializers.PrimaryKeyRelatedField(read_only=True,
allow_null=False,
pk_field=serializers.UUIDField(format='hex_verbose'))
And in every serializer that uses UUID I am having to use that.
My question is, how can I create a new class based on PrimaryKeyRelatedField so that I don't have to write all those parameters (read_only, allow_null...) ?
I am looking for something like:
id = BaseUUIDField()
Thanks
You can make an abstract class using the id which is a uuid field. Then inheret that model in your derived models.
import uuid
from django.db import models
//Abstract Model
class AbstractModel(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4, editable=False)
class Meta:
Abstract =True
//Derived Model
class YourDerivedModel(Abstract.Model):
//fields here
Hope this helps your query
According to the official Marshmallow docs, it's recommended to declare a Schema and then have a separate class that receives loaded data, like this:
class UserSchema(Schema):
name = fields.Str()
email = fields.Email()
created_at = fields.DateTime()
#post_load
def make_user(self, data):
return User(**data)
However, my User class would look something like this:
class User:
def __init__(name, email, created_at):
self.name = name
self.email = email
self.created_at = created_at
This seems like repeating myself unnecessarily and I really don't like having to write the attribute names three more times. However, I do like IDE autocompletion and static type checking on well-defined structures.
So, is there any best practice for loading serialized data according to a Marshmallow Schema without defining another class?
For vanilla Python classes, there isn't an out-of-box way to define the class for the schema without repeating the field names.
If you're using SQLAlchemy for example, you can define the schema directly from the model with marshmallow_sqlalchemy.ModelSchema:
from marshmallow_sqlalchemy import ModelSchema
from my_alchemy_models import User
class UserSchema(ModelSchema):
class Meta:
model = User
Same applies to flask-sqlalchemy which uses flask_marshmallow.sqla.ModelSchema.
In the case of vanilla Python classes, you may define the fields once and use it for both schema and model/class:
USER_FIELDS = ('name', 'email', 'created_at')
class User:
def __init__(self, name, email, created_at):
for field in USER_FIELDS:
setattr(self, field, locals()[field])
class UserSchema(Schema):
class Meta:
fields = USER_FIELDS
#post_load
def make_user(self, data):
return User(**data)
Unless you need to deserialize as a specific class or you need custom serialization logic, you can simply do this (adapted from https://kimsereylam.com/python/2019/10/25/serialization-with-marshmallow.html):
from marshmallow import Schema, fields
from datetime import datetime
class UserSchema(Schema):
name = fields.Str(required=True)
email = fields.Email()
created_at = fields.DateTime()
schema = UserSchema()
data = { "name": "Some Guy", "email": "sguy#google.com": datetime.now() }
user = schema.load(data)
You could also create a function in your class that creates a dict with validation rules, though it would still be redundant, it would allow you to keep everything in your model class:
class User:
def __init__(name, email, created_at):
self.name = name
self.email = email
self.created_at = created_at
#classmethod
def Schema(cls):
return {"name": fields.Str(), "email": fields.Email(), "created_at": fields.DateTime()}
UserSchema = Schema.from_dict(User.Schema)
If you need to strong typing and full validation functionality, consider flask-pydantic or marshmallow-dataclass.
marshmallow-dataclass offers a lot of similar validation features to marshmallow. It kind of ties your hands though. It doesn't have built-in support for custom fields/polymorphism (have to use using marshmallow-union instead) and doesn't seem to play well with stack-on packages like flask-marshmallow and marshmallow-sqlalchemy. https://pypi.org/project/marshmallow-dataclass/
from typing import ClassVar, Type
from marshmallow_dataclass import dataclasses
from marshmallow import Schema, field, validate
#dataclass
class Person:
name: str = field(metadata=dict(load_only=True))
height: float = field(metadata=dict(validate=validate.Range(min=0)))
Schema: ClassVar[Type[Schema]] = Schema
Person.Schema().dump(Person('Bob', 2.0))
# => {'height': 2.0}
flask-pydantic is less elegant from a validation standpoint, but offers many of the same features and the validation is built into the class. Note that simple validations like min/max are more awkward than in marshmallow. Personally, I prefer to keep view/api logic out of the class though. https://pypi.org/project/Flask-Pydantic/
from typing import Optional
from flask import Flask, request
from pydantic import BaseModel
from flask_pydantic import validate
app = Flask("flask_pydantic_app")
class QueryModel(BaseModel):
age: int
class ResponseModel(BaseModel):
id: int
age: int
name: str
nickname: Optional[str]
# Example 1: query parameters only
#app.route("/", methods=["GET"])
#validate()
def get(query:QueryModel):
age = query.age
return ResponseModel(
age=age,
id=0, name="abc", nickname="123"
)
You'll have to create the two classes, but the good news is you won't have to enter the attribute names multiple times in most cases. One thing I've found, if you are using Flask, SQLAlchemy, and Marshmallow, is that if you define some of the validation attributes in your Column definition, the Marshmallow Schema will automatically pick up on these and the validations supplied in them. For example:
import (your-database-object-from-flask-init) as db
import (your-marshmallow-object-from-flask-init) as val
class User(db.Model):
name = db.Column(db.String(length=40), nullable=False)
email = db.Column(db.String(length=100))
created_at = db.Column(db.DateTime)
class UserSchema(val.ModelSchema):
class Meta:
model = User
In this example, if you were take a dictionary of data and put it into UserSchema().load(data) , you would see errors if, in this example, name didn't exist, or name was longer than 40 characters, or email is longer than 100 characters. Any custom validations beyond that you'd still have to code within your schema.
It also works if you've created the model class as an extension of another model class, carrying over its attributes. For example, if you wanted every class to have created/modified information, you could put those attributes in the parent model class and the child would inherit those along with their validation parameters. Marshmallow doesn't allow your parent model to have a schema, so I don't have information on custom validations there.
I know you've probably already completed your project, but I hope this helps for other developers that come across this.
Relevant pip list:
Flask (1.0.2)
flask-marshmallow (0.9.0)
Flask-SQLAlchemy (2.3.2)
marshmallow (2.18.0)
marshmallow-sqlalchemy (0.15.0)
SQLAlchemy (1.2.16)
I just started learning DRF and I was wondering what's the best approach to build the model for users. I want to serve the data from my rest API to mobile so getting out specific fields out of the API is crucial. Is it possible to nest in the same model and say retrieve the "email" for data set with id 1?
serializers.py
from .models import UserDatabase
from rest_framework import serializers
class UserDatabaseSerializer(serializers.ModelSerializer):
"""
Serializing the user database with our fields
Adding them directly to the database
"""
class Meta:
model = UserDatabase
fields = ("id","email","first_name","last_name","passw")
models.py
from django.db import models
class UserDatabase(models.Model):
"""
"""
id = models.IntegerField(primary_key=True)
email = models.CharField(max_length=320)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
passw = models.CharField(max_length=60)
EDIT What I meant to say is, how should my models and serializers look in order to be able to query a specific field like "email" after I find a user by querying his "id".
I wrote an __init__ method for one of my models that adds some auxiliary information to the object by dynamically adding an attribute to the object that does not reflect a column in the database:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def __init__(self, *args, **kwargs):
self.aux_info = "I'm not in the database!"
This seemed to be working fine, but I found a case where it does not work. I have some code in a view where I set a status variable and package up a list of MyModels into json like so:
from django.core import serializers
from django.utils import simplejson
...
# have to use serializers for django models
serialized_items = serializers.serialize("json", itemlist)
data["items"] = serialized_items # serialized_items is now a string
data["status"] = status
# package up data dict using simplejson for python objects
resp = simplejson.dumps(data)
return HttpResponse(resp, mimetype="application/javascript")
The problem seems to be that django's serializers only serialize the model fields and not all attributes of the object so aux_info does not come through. I'm also pretty sure that using both serializers and simplejson is not the right way to do this. Thanks for any help!
Try usung the serialiser's optional fields argument.
serialized_items = serializers.serialize("json", itemlist, fields=['.....', 'aux_info'])
May i also suggest that using the __init__ method to add fields is considdered bad form in django and would be much better achieved like so:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def aux_info(self):
return "I'm not in the database!"