How to make model flask marshmallow_sqlalchemy desrialize related object - flask

I have this code:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class Author(Base):
__tablename__ = "authors"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False)
books = relationship("Book", backref=backref("author", lazy="joined"),
foreign_keys="Book.author_id", lazy="dynamic")
def __repr__(self):
return "<Author(name={self.name!r})>".format(self=self)
class Book(Base):
__tablename__ = "books"
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.String)
author_id = sa.Column(sa.Integer, sa.ForeignKey("authors.id"))
Base.metadata.create_all(engine)
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field, SQLAlchemyAutoSchema
class AuthorSchema(SQLAlchemyAutoSchema):
class Meta:
model = Author
load_instance = True # Optional: deserialize to model instances
# dump_only = ("id",)
include_fk = True
class BookSchema(SQLAlchemyAutoSchema):
class Meta:
model = Book
load_instance = True
# dump_only = ("id",)
include_fk = True
author = Author(name="Chuck Paluhniuk")
author_schema = AuthorSchema()
book = Book(title="Fight Club", author=author)
book_schema = BookSchema()
session.add(author)
session.add(book)
session.commit()
dump_data_author = author_schema.dump(author)
print(dump_data_author)
dump_data_book = book_schema.dump(book)
print(dump_data_book)
When I run the code it prints:
{'id': 1, 'name': 'Chuck Paluhniuk'}
{'id': 1, 'author_id': 1, 'title': 'Fight Club'}
I want to change the code such that it prints:
{'id': 1, 'name': 'Chuck Paluhniuk', 'books': [{'id': 1, 'author_id': 1, 'title': 'Fight Club'}]}
{'id': 1, 'author_id': 1, 'title': 'Fight Club'}
How should I do this?
I want to have control over deserializing related objects.
I am also curious about some othe settings in the meta such load_only and dump_only and excelude, etc.

In my opinion, it is necessary to define nested fields in order to include relationships in the output in this way. You can define that the relationships are included automatically, but this only applies to the primary keys. In order not to use an override of a field, I have not included the relationships.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from flask_marshmallow import Marshmallow
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Author(db.Model):
__tablename__ = "authors"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
books = db.relationship("Book",
backref=db.backref("author", lazy="joined"),
foreign_keys="Book.author_id",
lazy="dynamic"
)
class Book(db.Model):
__tablename__ = "books"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
author_id = db.Column(db.Integer, db.ForeignKey("authors.id"))
class BookSchema(SQLAlchemyAutoSchema):
class Meta:
model = Book
include_fk = True
load_instance = True
sqla_session = db.session
class AuthorSchema(SQLAlchemyAutoSchema):
class Meta:
model = Author
load_instance = True
# include_relationships = True # See below
sqla_session = db.session
books = ma.Nested(BookSchema, many=True)
with app.app_context():
db.drop_all()
db.create_all()
author = Author(name="Chuck Paluhniuk")
book = Book(title="Fight Club", author=author)
db.session.add(author)
db.session.add(book)
db.session.commit()
author_schema = AuthorSchema()
book_schema = BookSchema()
dump_data_author = author_schema.dump(author)
print(dump_data_author)
dump_data_book = book_schema.dump(book)
print(dump_data_book)
json = [
{'author_id': 1, 'title': 'Fight Club', 'id': 1}
]
schema = BookSchema(many=True)
print(schema.load(json))
json = [
{'books': [{'author_id': 1, 'title': 'Fight Club', 'id': 1}], 'name': 'Chuck Paluhniuk', 'id': 1}
]
schema = AuthorSchema(many=True)
print(schema.load(json))

Related

get id not value from SqlAlchemy_wtforms QuerySelectField

I have been trying to make a relationship between two table and have a QuerySelectFiled to select a field from the other table, I have managed to do so, but the problem came when I wanted to submit the field I keep getting :
InterfaceError: <unprintable InterfaceError object>
after debugging the issue I found out that when the form is submitted the value of the field is being submitted not the id, I have solved that by saying in my model
self.firstAuthor = firstAuthor.id
solved the issue but if I choose the empty value it will break because the empty field does not have property id.
so can someone suggest how to do that?
here is my form:
from wtforms_alchemy import QuerySelectField, QuerySelectMultipleField
from ....module1.authors.author.authorModel import Author
from flask_wtf import FlaskForm
from wtforms import SubmitField, HiddenField, BooleanField, SelectField, StringField, FileField, IntegerField, DateTimeField
from datetime import datetime
from wtforms.validators import DataRequired
def getAuthor():
return Author.query
class PublicationsForm(FlaskForm):
id = HiddenField()
title = StringField('Title', validators=[DataRequired()])
category = SelectField('Category', choices=[('', ''),('Journal', 'Journal'), ('Conference', 'Conference'), ('Talk', 'Talk'),('Talk2', 'Talk2'),('Talk3', 'Talk3')])
year = DateTimeField('Year', format='%Y')
publisher = BooleanField('Publisher')
volume = IntegerField('Volume')
issue = IntegerField('Issue')
pages = StringField('Pages')
location = StringField('Location')
note = StringField('Note')
fullCitation = FileField('FullCitation')
fullSource = FileField('FullSource')
finalVersion = FileField('FinalVersion')
firstAuthor = QuerySelectField("FirstAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
secondAuthor = QuerySelectField("SecondAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
thirdAuthor = QuerySelectField("ThirdAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
fourthAuthor = QuerySelectField("FourthAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
fifthAuthor = QuerySelectField("FifthAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
sixthAuthor = QuerySelectField("SixthAuthor", query_factory=getAuthor, get_label="lastName", allow_blank=True, blank_text='')
submit = SubmitField("Save")
here is my model:
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from app import db
class Publications(db.Model):
id = db.Column(Integer, primary_key=True)
title = db.Column(String, nullable=False)
category = db.Column(String, nullable=False)
year = db.Column(db.DateTime)
publisher = db.Column(Boolean)
volume = db.Column(Integer)
issue = db.Column(String)
pages = db.Column(String)
location = db.Column(String)
note = db.Column(String)
fullCitation = db.Column(String)
fullSource = db.Column(String)
finalVersion = db.Column(String)
issue = db.Column(db.Text)
firstAuthor = db.Column(db.Integer, db.ForeignKey('author.id'))
secondAuthor = db.Column(db.Integer, db.ForeignKey("author.id"),nullable=True )
thirdAuthor = db.Column(db.Integer, db.ForeignKey("author.id"),nullable=True )
fourthAuthor = db.Column(db.Integer, db.ForeignKey("author.id"),nullable=True )
fifthAuthor = db.Column(db.Integer, db.ForeignKey("author.id"),nullable=True )
sixthAuthor = db.Column(db.Integer, db.ForeignKey("author.id"),nullable=True )
def __init__(self, title, category, year, publisher, volume, issue, pages, location, note, fullCitation, fullSource, finalVersion, firstAuthor, secondAuthor, thirdAuthor, fourthAuthor, fifthAuthor, sixthAuthor):
self.title = title
self.category = category
self.year = year
self.publisher = publisher
self.volume = volume
self.issue = issue
self.pages = pages
self.location = location
self.note = note
self.fullCitation = fullCitation
self.fullSource = fullSource
self.finalVersion = finalVersion
self.firstAuthor = firstAuthor
self.secondAuthor = secondAuthor
self.thirdAuthor = thirdAuthor
self.fourthAuthor = fourthAuthor
self.fifthAuthor = fifthAuthor
self.sixthAuthor = sixthAuthor
def __repr__(self):
return self.title
here is my view:
from flask import render_template, request, flash, redirect, url_for
from . import publications_blueprint
from .publicationsForm import PublicationsForm
from .publicationsModel import Publications
from app import db
#publications_blueprint.route("/publications", methods=["GET", "POST"])
def createPublications():
form = PublicationsForm(request.form)
publicationss = Publications.query.all()
if request.method == "POST" and form.validate_on_submit():
publications = Publications(form.title.data, form.category.data, form.year.data, form.publisher.data, form.volume.data, form.issue.data, form.pages.data, form.location.data, form.note.data, form.fullCitation.data, form.fullSource.data, form.finalVersion.data, form.firstAuthor.data, form.secondAuthor.data, form.thirdAuthor.data, form.fourthAuthor.data, form.fifthAuthor.data, form.sixthAuthor.data)
db.session.add(publications)
db.session.commit()
flash("Added Publications Successfully")
return redirect(url_for("publications.createPublications"))
return render_template("publications/publications.html", title="Publicationss", form=form, publicationss=publicationss)
#publications_blueprint.route("/updatePublications/<int:publications_id>", methods=["GET", "POST"])
def updatePublications(publications_id):
publications = Publications.query.get(publications_id)
form = PublicationsForm(request.form, obj=publications)
if request.method == "POST" and form.validate_on_submit():
publications.title = form.title.data
publications.category = form.category.data
publications.year = form.year.data
publications.publisher = form.publisher.data
publications.volume = form.volume.data
publications.issue = form.issue.data
publications.pages = form.pages.data
publications.location = form.location.data
publications.note = form.note.data
publications.fullCitation = form.fullCitation.data
publications.fullSource = form.fullSource.data
publications.finalVersion = form.finalVersion.data
publications.firstAuthor = form.firstAuthor.data
publications.secondAuthor = form.secondAuthor.data
publications.thirdAuthor = form.thirdAuthor.data
publications.fourthAuthor = form.fourthAuthor.data
publications.fifthAuthor = form.fifthAuthor.data
publications.sixthAuthor = form.sixthAuthor.data
db.session.commit()
flash("Updated Publications Successfully")
return redirect(url_for("publications.createPublications"))
publicationss = Publications.query.all()
return render_template("publications/publications.html", title="Publications", form=form, publicationss=publicationss)
#publications_blueprint.route("/deletePublications/<int:publications_id>", methods=["GET", "POST"])
def deletePublications(publications_id):
publications = Publications.query.get(publications_id)
db.session.delete(publications)
db.session.commit()
return redirect(url_for("publications.createPublications"))
In a similar case, I also use allow_blank=True in the model for a QuerySelectField search_target. I check manually if there was something selected in the view:
if formfilter.search_target.data:
search_target = formfilter.search_target.data.id
else:
....

marshmallow-sqlalchemy Unable to get child model's data from parent model_schema

I'm trying to use marshmallow-sqlalchemy to create an API
However, When I dump data, It shows only parent models data only
like {'id': 1, 'date': '2019-09-01'}
How do I get company data like {'id': 1, 'date': '2019-09-01', 'orders': {'id': 1, 'date': '2019-09-01'}, {'id': 1, 'date': '2019-09-01'}}
the model is
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
company_id = db.Column(db.Integer, db.ForeignKey('user_company.id'), nullable=False)
company = db.relationship('UserCompany', backref='orders', lazy=True)
date = db.Column(db.DateTime(), server_default=func.now())
class UserCompany(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
company_name = db.Column(db.String(length=20))
And the schema is
class OrderSchema(ModelSchema):
class Meta:
sqla_session = db.Session
fields = ('id', 'date')
model = Order
order_schema = OrderSchema()
orders_schema = OrderSchema(many=True)
class UserCompanySchema(ModelSchema):
orders = Nested(OrderSchema, many=True)
class Meta:
fields = ('id', 'company_name')
model = UserCompany
sqla_session = db.session
user_company_schema = UserCompanySchema()
and the View is
company = UserCompany.query.get(current_user.company.id)
print(user_company_schema.dump(company))
{'id': 1, 'date': '2019-09-01'}
What I want is
{'id': 1, 'date': '2019-09-01', 'orders': {'id': 1, 'date': '2019-09-01'}, {'id': 1, 'date': '2019-09-01'}}
You need to explicitly set all of desired fields in Meta class if you want them to be present in dumps.
class UserCompanySchema(ModelSchema):
orders = Nested(OrderSchema, many=True)
class Meta:
fields = ('id', 'company_name', 'orders')
model = UserCompany
sqla_session = db.session

ma.Nested probably does not work sqlalchemy and marshmallow

I have one to many relationships between tables in a database and I want to get the data using an API.
I think that ma.Nested does not work because I don't get all the fields
can anyone help me?
I get only this :
[
{
"IsRef": false,
"commands": [
"299d7f0b-721c-484b-9448-072716a5fd70",
"382c5d9f-99a1-4aa2-ac30-96084e202fad",
"299d7f0b-721c-484b-9448-072716a5fd75",
"382c5d9f-99a1-4aa2-ac30-96084e202fak"
],
"filename": "uiyg",
"version": 8
}
]
this is the database model :
class File(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), unique=True)
version = db.Column(db.Integer, unique=True)
IsRef = db.Column(db.Boolean)
commands = db.relationship('Command', backref='log', lazy='joined')
def __init__(self, filename, version, IsRef):
self.filename = filename
self.version = version
self.IsRef = IsRef
class Command(db.Model):
id_command = db.Column(db.String(255), primary_key=True, autoincrement=False)
name = db.Column(db.String(255))
log_id = db.Column(db.Integer, db.ForeignKey('file.id'))
status = db.Column(db.Boolean)
events = db.relationship('Event', backref='com', lazy='joined')
def __init__(self, id_command, name, log_id, status):
self.id_command = id_command
self.name = name
self.log = log_id
self.status = status
class Event(db.Model):
id_event = db.Column(db.Integer, primary_key=True)
event_name = db.Column(db.String(255))
seq_number = db.Column(db.Integer)
com_id = db.Column(db.Integer, db.ForeignKey('command.id_command'))
def __init__(self, event_name, seq_number, com_id):
self.event_name = event_name
self.seq_number = seq_number
self.com = com_id
this is the schema :
class EventSchema(ma.ModelSchema):
class Meta:
model = Event
fields = ('event_name', 'seq_number')
class CommandSchema(ma.ModelSchema):
class Meta:
model = Command
events = ma.Nested(EventSchema)
fields = ('id_command', 'name', 'status', 'events')
class LogSchema(ma.ModelSchema):
class Meta:
model = File
commands = ma.Nested(CommandSchema)
fields = ('filename', 'version', 'IsRef', 'commands')
Log_schema = LogSchema(many=True)
this is the API :
#app.route("/getall/<version>", methods=["GET"])
def get_all(version):
alle = File.query.filter_by(version=version)
return Log_schema.jsonify(alle)
this question is solved. I've changed 'lazy ='joined'' to 'lazy='dynamic'' and I've put ma.Nested out of the Meta class

How to paginate a secondary table using SQLALCHEMY in Flask?

I am trying to paginate a secondary table for Client but it doesn't work, the results always more than the per_page parameter that by default shows only three items per page .
Here is my models.py:
subscribers = db.Table(
'clients_subscribed',
db.Column('client_id', db.Integer, db.ForeignKey('client.id', ondelete='CASCADE')),
db.Column('user_id', db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
)
class Client(db.Model, UserMixin):
id = db.Column(db.Integer(), primary_key=True)
public_id = db.Column(db.String(50), default=uuid.uuid4)
name = db.Column(db.String())
subscribed_users = db.relationship(
'User',
secondary=subscribers,
backref=db.backref('user', passive_deletes=True, lazy='dynamic')
)
Here is also my views.py :
from flask import current_app
current_app.config['SUBSCRIBERS_PER_PAGE'] = 3
#api_route.route('/client/subscribers/<string:category>/<string:client_id>', methods=['GET'])
#token_required
def subscribers(current_user, category, client_id):
if request.method == 'GET':
page = request.args.get('page', 1, type=int)
client = Client.query.filter_by(public_id=client_id).first()
if not client:
raise InvalidUsage(u'404, not found!', status_code=404)
pagination = # Paginate Client.subscribed_users
psubscribers = pagination.items
subscribers = {}
prev = None
if pagination.has_prev:
prev = url_for('api.subscribers', category=category, client_id=client_id, page=page-1, _external=True)
next = None
if pagination.has_next:
next = url_for('api.subscribers', category=category, client_id=client_id, page=page+1, _external=True)
for client in client.subscribed_users:
subscribers_case = {
'id': client.id,
'public_id': client.public_id,
'image': '/static/img/'+client.image if client.image else '/static/img/logo.png',
'name': client.name,
}
subscribers[client.public_id] = subscribers_case
return jsonify({
'subscribers' : subscribers,
'prev' : prev,
'next': next,
'count' : pagination.total
})
return jsonify({'error' : 'No data!'})
Also here is the class User in models:
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer(), primary_key=True)
public_id = db.Column(db.String(50), default=uuid.uuid4)
name = db.Column(db.String())
family = db.Column(db.String())
bio = db.Column(db.String())
tele = db.Column(db.String(), unique=True)
password = db.Column(db.String())
clients = db.relationship('Client', backref='user', passive_deletes=True, lazy='dynamic')
news = db.relationship('News', backref='user', passive_deletes=True, lazy='dynamic')
notifications = db.relationship('Notification', backref='user', passive_deletes=True, lazy='dynamic')
image = db.Column(db.String(), nullable=True)
slug = db.Column(db.String())
roles = db.relationship(
'Role',
secondary=roles,
backref=db.backref('users', lazy='joined',
passive_deletes=True,
single_parent=True)
)
subscribed_clients = db.relationship(
'Client',
secondary=subscribers,
backref=db.backref('client', passive_deletes=True, lazy='joined')
)
Any suggestions guys how to make that work !!!
You need to use lazy=dynamic in the relationship in order to get a Query object back. Documentation here, and another StackOverflow question about this here.

Serialize related objects in django rest framework

How can I serialize related objects in DRF. Am having three models auth_user (User), ModelA, ModelB.
My models:
class ModelA(models.Model):
user = models.OneToOneField(auth_user, related_name = 'modelA')
name = models.CharField(max_length = 30)
class ModelB(models.Model):
owner = models.OneToOneField(auth_user)
user = models.OneToOneField(auth_user, related_name = 'modelB')
type = models.ForeignKey(ModelD)
cost = models.IntegerField()
class ModelD(models.Model):
type_desc = models.CharField(max_length = 40)
My serialilzers:
class A(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = ('name', )
class B(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = ('type', 'cost', )
class AuthUserSerilaizer(serializers.ModelSerializer):
userA = serializers.RelatedField(source = 'modelA')
userB = serializers.RelatedField(source = 'modelB')
class Meta:
model = User
fields = ('email', 'password', 'userA', 'userB', )
write_only_fields = ('password',)
#transaction.commit_manually
def restore_object(self, attrs, instance = None):
try:
user = User.objects.create_user(email = attrs.get('email'), password = attrs.get('password'))
modela = ModelA(user =user, name = attrs.get('name'))
modela.save()
transaction.commit()
return User(email = attrs.get('email'))
except Exception ,e:
transaction.rollback()
print repr(e)
JSON I'm passing
data = {'email':'123#gmail.com,
'password' : 'dummy',
'userA' : {'name' :'123'},
'userB':{'type':1,'cost':'100'}
Whenever am making a POST request the 'attrs' gets only email and password not userA and userB, why? How can I deserialize and create data serially in the respective tables.
try this in your view
from django.core.serializers.json import DjangoJSONEncoder
import json
response = json.dumps(data,cls=DjangoJSONEncoder)
return HttpResponse(response, mimetype="application/json")
and in success function you can access using
usera_name = response.userA.name
userb_type = response.userB.type
userb_cost = response.userB.cost