Override default Model View - flask-admin

How can I override the Model View so by default all Model Views have exact same settings which I override?
For example:
I have 5 model views linked to some db models which are all custom,but I want all these 5 to have some default settings so I won't have to write code for each view in the ModelView class.

Use inheritance:
class BaseView(ModelView):
# Add common functionality here
pass
class ProductView(BaseView):
# Add specific functionality here
pass
class CategoryView(BaseView):
# Add specific functionality here
pass
A simple one file example below.
Class BaseView turns on can_view_details and formats the description column in upper case.
Notice the difference between ProductView, which inherits from BaseView, and ProductNotInheritedView which inherits directly from ModelView.
Note the code uses the Faker library to generate random data.
from flask import Flask
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from faker import Faker
from flask_admin import Admin
app = Flask(__name__)
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
#app.route('/')
def index():
return 'Click me to get to Admin!'
class Supplier(db.Model):
__tablename__ = 'supplier'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(100), nullable=False)
description = db.Column(db.UnicodeText(), nullable=True)
products = db.relationship("Product", back_populates="supplier")
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.name
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(100), nullable=False)
code = db.Column(db.Unicode(32), nullable=False)
description = db.Column(db.UnicodeText(), nullable=True)
supplier_id = db.Column(db.Integer, db.ForeignKey('supplier.id'), index=True, nullable=False)
supplier = db.relationship(Supplier, back_populates='products')
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.name
class BaseView(ModelView):
can_view_details = True
column_formatters = {
'description': lambda v, c, m, p: m.description.upper(),
}
class SupplierView(BaseView):
column_list = ('name', 'description', 'products')
class ProductView(BaseView):
pass
class ProductNotInheritedView(ModelView):
pass
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(SupplierView(Supplier, db.session))
admin.add_view(ProductView(Product, db.session))
admin.add_view(
ProductNotInheritedView(Product, db.session, name='Product Not Inherited', endpoint='product-not-inherited'))
#app.before_first_request
def build_sample_db():
db.drop_all()
db.create_all()
fake = Faker()
_suppliers = []
for _ in range(20):
_supplier = Supplier(
name=fake.company(),
description=fake.paragraph(nb_sentences=fake.random.randint(1, 10))
)
_suppliers.append(_supplier)
for _ in range(fake.random.randint(1, 10)):
_supplier.products.append(
Product(
name=' '.join(fake.words(nb=fake.random.randint(1, 5))),
description=fake.paragraph(nb_sentences=fake.random.randint(1, 10)),
code=fake.isbn10(separator="-")
)
)
db.session.add_all(_suppliers)
db.session.commit()
if __name__ == '__main__':
app.run(port=5000, debug=True)

Related

Two InlineFormAdmin for one model class not working

I have one model class named Job and one model class named Node. One job can have many nodes of the type source or destination.
I have a SourceNodeInlineModelForm and a DestinationNodeInlineModelForm that both are based on model class Node. These works as expected if I use one of them in inline_models but when I use both of the only the last specified in inline_models is displayed. In below only DestinationNodeInlineModelForm is displayed.
class Job(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64))
class Node(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(16))
hostname = db.Column(db.Unicode(128))
port = db.Column(db.Integer)
job_id = db.Column(db.Integer, db.ForeignKey(Job.id))
job = db.relation(Job, backref='jobs')
class CustomInlineFieldListWidget(RenderTemplateWidget):
def __init__(self):
super(CustomInlineFieldListWidget, self).__init__('field_list.html')
class CustomInlineModelFormList(InlineModelFormList):
widget = CustomInlineFieldListWidget()
def display_row_controls(self, field):
return False
class CustomInlineModelConverter(InlineModelConverter):
inline_field_list_type = CustomInlineModelFormList
class SourceNodeInlineModelForm(InlineFormAdmin):
form_label = 'Source'
form_extra_fields = {
'type': HiddenField('type', default='source')
}
def __init__(self):
return super(SourceNodeInlineModelForm, self).__init__(Node)
class DestinationNodeInlineModelForm(InlineFormAdmin):
form_label = 'Destination'
form_extra_fields = {
'type': HiddenField('type', default='destination')
}
def __init__(self):
return super(DestinationNodeInlineModelForm, self).__init__(Node)
class JobAdmin(ModelView):
inline_model_form_converter = CustomInlineModelConverter
inline_models = (SourceNodeInlineModelForm(), DestinationNodeInlineModelForm())
def __init__(self):
super(JobAdmin, self).__init__(Job, db.session, name='Jobs')
Only the last entry in inline_models is being rendered. I expected both to be rendered.

Writing to multiple tables using sqlalchemy, fastapi, pydantic postgres

First API I've built so bear with me, I currently have a FastAPI that is supposed to save a record of an event and when it happened, as well as a list of people who assisted with each event. Currently, my crud.py "post" command currently only posts to 'test', but I also need it to post names of those who helped to 'whohelped'. I've tried to make 'whohelped.event_token' the foreign key of 'Save_Info.token'. A check on whether my models and schema are correctly made would be greatly appreciated. The main issue is I'm totally lost how to make "post" make changes to both tables at once.
models.py
from .database import Base
from sqlalchemy import Column, String, Integer, Date, ForeignKey
from sqlalchemy.orm import relationship
class Save_Info(Base):
__tablename__ = 'test'
token = Column(Integer, primary_key = True, autoincrement = True)
how = Column(String)
date = Column(Date)
children = relationship("Who_Helped",back_populates="test")
class Who_Helped(Base):
__tablename__ = 'whohelped'
id = Column(Integer, primary_key = True, autoincrement = True)
event_token = Column(Integer, ForeignKey('test.token'))
who_helped = Column(String)
schema.py
from pydantic import BaseModel
from typing import Optional, List
from sqlalchemy.orm import relationship
from sqlalchemy import DateTime
class Who_Helped(BaseModel):
id: int
event_token: int
who_helped: Optional[str]
class Save_Info(BaseModel):
token: int
how: str
date: str
class Config:
orm_mode = True
crud.py
from sqlalchemy.orm import Session
from . import schema, models
def post_info(db: Session, info: schema.Save_Info):
device_info_model = models.Save_Info(**info.dict())
db.add(device_info_model)
db.commit()
db.refresh(device_info_model)
return device_info_model
def get_info(db: Session, token: int = None):
if token is None:
return db.query(models.Save_Info).all()
else:
return db.query(models.Save_Info).filter(models.Save_Info.token == token).first()
def error_message(message):
return {
'error': message
}
main.py
from fastapi import FastAPI, Depends, HTTPException
from .database import SessionLocal, engine
from sqlalchemy.orm import Session
from .schema import Save_Info
from . import crud, models
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
def db():
try:
db = SessionLocal()
yield db
finally:
db.close()
#app.post('/device/info')
def post_info(info: Save_Info, db=Depends(db)):
object_in_db = crud.get_info(db, info.token)
if object_in_db:
raise HTTPException(400, detail= crud.error_message('This account of saving the world already exists'))
return crud.post_info(db,info)
#app.get('/device/info/{token}')
def get_info(token: int, db=Depends(db)):
info = crud.get_info(db,token)
if info:
return info
else:
raise HTTPException(404, crud.error_message('No info found for this account of saving the world {}'.format(token)))
#app.get('/device/info')
def get_all_info(db=Depends(db)):
return crud.get_info(db)

Integer range in Flask REST API using SQLAlchemy

I am creating a REST API using Flask, SQLAlchemy, and Marshmallow. I have defined my Product Model in app.py as:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
import os
# Initialize App
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
# Database Setup
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Init db
db = SQLAlchemy(app)
# Init marshmallow
ma = Marshmallow(app)
# Product Class/Model
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
price = db.Column(db.Integer)
qty = db.Column(db.Integer)
def __init__(self, price, qty):
self.price = price
self.qty = qty
# Product Schema
class ProductSchema(ma.Schema):
class Meta:
fields = ('id', 'price', 'qty')
# Init Schema
product_schema = ProductSchema()
products_schema = ProductSchema(many=True)
# Create Product
#app.route('/product', methods=['POST'])
def add_product():
price = request.json['price']
qty = request.json['qty']
new_product = Product(price, qty)
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
# Run the Server
if __name__ == '__main__':
app.run(debug=True)
I have to perform the following logic:
Setting price value between 0 - 100
Setting qty value between 0 - 100
If success return 200, if anything wrong return 500.
I am not able to set Integer value between the given range by db.Integer([0, 100]) as its giving me error:
TypeError: Integer() takes no arguments
How do I implement the above logic?
Edit: I've misunderstood the question and made a new function.
def ExpectedRange(var1):
return 200 if var1 in range(0,100) else 500
# Create Product
#app.route('/product', methods=['POST'])
def add_product():
price = request.json['price']
qty = request.json['qty']
if ExpectedRange(price) and ExpectedRange(qty) == 200:
new_product = Product(price, qty)
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
#else:
# Show error. I recommend you using the method 'flash' in flask.
I think the problem with your code by using db.Integer([0, 100]) as a way to find the value between 0 and 100, instead, what you should be doing is by using range with the help of a method called randrange from the library random. With all due respect, I actually don't know what you are trying to accomplish, if I am wrong, please correct me in the comments and I'll correct my post.
What I recommend you doing is to not set the price and qty in the model class, but rather, in an entirely different function and using your model class to create the elements within your database. For example:
from random import randrange
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
price = db.Column(db.Integer)
qty = db.Column(db.Integer)
def ProductRange(range1, range2):
return randrange(range1, range2)
print(ProductRange(1,100))
What the function ProductRange will do is to choose the range between the variable range1 and range2. As for returning 200 and 500, I am not sure what you could use with this value, but I recommend doing boolean. If it is needed, 200 and 500 is simply a constant, and you could easily implement it via putting it in a function rather than using the returned value to calculate things. So, how would you use the ProductRange function? Just follow the code below.
from random import randrange
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
product_name = db.Column(db.String) # Ignore this line, this just for the /addpost route to get the method POST
price = db.Column(db.Integer)
qty = db.Column(db.Integer)
def ProductRange(range1, range2):
return randrange(range1, range2)
# This route is just an example of how you would use the function ProductRange
#app.route('/addpost', methods=['POST'])
def addpost():
product_name = request.form['product_name']
price = ProductRange(1,100)
qty = ProductRange(1,100)
post = Product[product_name=product_name, price=price, qty=qty]
db.session.add(post)
db.session.commit()
return redirect(url_for('index'))
If it doesn't work, please comment down below for me to help you further with this question of yours. I wish you good luck.
since you have installed marshmallow, install the marmallow-sqlalchemy and use SQLAlchemyAutoSchema feature which will allow to you to refer directly to the model and create an instance after successful load of the json object sent in request body, plus you can define your own constraints in the schema class. the marshmallow conf. will look like:
from marshmallow import ValidationError, fields
from marshmallow.validate import Range
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
ma = Marshmallow(app)
# to return marshmallow parsing error
#app.errorhandler(ValidationError)
def handle_marshmallow_validation(err):
print(err)
return jsonify(err.messages), 400
# Product Schema
class ProductSchema(ma.SQLAlchemyAutoSchema):
id = fields.Integer(required=False)
price = fields.Integer(required=True, validate=[Range(max=100, error="Value must be 100 or less")])
qty = fields.Integer(required=True, validate=[Range(max=100, error="Value must be 100 or less")])
class Meta:
model = Product
load_instance = True
now the ressource will look like:
# Create Product
#app.route('/product', methods=['POST'])
def add_product():
# here we can check the payload validity, parse it and transform it directly to instance
product_json = request.get_json()
new_product = product_schema.load(product_json)
db.session.add(new_product)
db.session.commit()
return product_schema.dump(new_product)
now if you sent value outside the range you will receive response like this
{
"price": [
"Value must be 100 or less"
],
"qty": [
"Value must be 100 or less"
]
}

Cannot access data after full text search using sqlalchemy, postgres and flask

I would like to search my postgres data base using postgres build-in full text search capability. In my app I have a set of posts stored according to title, content and date.
I think I can search the database using tsvector, but cannot retrieve the data from the results; i.e. the title, the content and the date. Could anyone help me, please?
import json, sys
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.dialects import postgresql
from sqlalchemy.sql.expression import cast, func
from sqlalchemy import Index
def create_tsvector(*args):
exp = args[0]
for e in args[1:]:
exp += ' ' + e
return func.to_tsvector('english', exp)
app = Flask(__name__)
app.config['SECRET_KEY'] = 'some_key'
app.config["SQLALCHEMY_DATABASE_URI"] = 'postgresql:somedb'
db = SQLAlchemy(app)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Text, nullable=False)
content = db.Column(db.Text, nullable=False)
date = db.Column(db.Text,unique=False)
__ts_vector__ = create_tsvector(
cast(func.coalesce(content, ''), postgresql.TEXT)
)
__table_args__ = (Index('idx_post_fts', __ts_vector__, postgresql_using='gin'), )
def __repr__(self):
return f"Post('{self.title}', '{self.date}')"
if len(sys.argv) > 1:
filename1 = sys.argv[1]
infile=open(filename1,'r')
posts=json.load(infile)
infile.close()
List=list(posts)
art = 0
for j in range(0,len(List)):
if j % 10 == 0:
print(j)
title= posts[List[art]]['Title']
date = posts[List[art]]['Posted']
content=posts[List[art]]['Text']
post = Post(title=title, date=date, content=content)
db.session.add(post)
db.session.commit()
art+=1
from sqlalchemy.dialects.postgresql import TSVECTOR
from sqlalchemy import select, cast
posts = Post.__ts_vector__.match("bicycle", postgresql_regconfig='english')
print(posts)

mongoengine connection and multiple databases

I have 2 databases I want to query from, but I only get results from one. I'm using mongoengine with python and graphene (it's my first time). I've exhausted my search and I don't understand how I can resolve this issue. Here is my code:
import graphene
from mongoengine import Document, connect
from mongoengine.context_managers import switch_collection
from mongoengine.fields import (
StringField,
UUIDField,
IntField,
FloatField,
BooleanField,
)
from graphene_mongo import MongoengineObjectType
from mongoengine.connection import disconnect
class UserModel(Document):
meta = {"collection": "users"}
userID = UUIDField()
first_name = StringField()
last_name = StringField()
class Users(MongoengineObjectType):
class Meta:
model = UserModel
class UsersQuery(graphene.ObjectType):
users = graphene.List(Users)
user = graphene.Field(Users, userID=graphene.UUID())
def resolve_users(self, info):
db = connect("users")
users = list(UserModel.objects.all())
db.close()
return users
def resolve_user(self, info, userID):
return UserModel.objects(userID=userID).first()
users_schema = graphene.Schema(query=UsersQuery)
import graphene
from mongoengine import Document, connect
from mongoengine.fields import StringField, UUIDField
from graphene_mongo import MongoengineObjectType
from mongoengine.connection import disconnect
class Workout(Document):
meta = {"collection": "workouts"}
workoutID = UUIDField()
workout_label = StringField()
class Workouts(MongoengineObjectType):
class Meta:
model = Workout
class Query(graphene.ObjectType):
workouts = graphene.List(Workouts)
workout = graphene.Field(Workouts, workoutID=graphene.UUID())
def resolve_workouts(self, info):
db = connect("workouts")
wks = list(Workout.objects.all())
db.close()
return wks
def resolve_workout(self, info, workoutID):
return Workout.objects(workoutID=workoutID).first()
workouts_schema = graphene.Schema(query=Query)
Now when I have my python server up, mongod running I can hit the /workouts and it will return the array I need. But /users will not return the results.
I get no errors, nothing is wrong with my graphene query.
I can only get one of the queries to work at once.
I have tried using alias, not closing the connections, declaring the connect at the top level even before class UserModel or Workout.
If each of your model is bound to a different database. You should use something like this (cfr docs):
connect('workouts', alias='dbworkouts') # init a connection to database named "workouts" and register it under alias "dbworkouts"
connect('users', alias='dbusers')
class Workout(Document):
meta = {"db_alias": "dbworkouts"}
workoutID = UUIDField()
...
class UserModel(Document):
meta = {"db_alias": "dbusers"}
userID = UUIDField()
...