I am new to Flask and bit confused about the database modeling for it. Please have my apologies if by any mean this isn't a question eligible for post.
I need to create a multi choice model field in Flask and I need to be able to access it from backend admin panel to set values for it. It does shows options in docs with WTF forms to create multiple choice field. I am confused that how to create forms attached to Database. Can someone clear it up for me because I am a Django user and in Django Forms and ModelForms have different approach so trying to understand what would it be in Flask. How to render Database based forms in Flask? How would I create a multi choice field with database created for it. Please help.
What you are looking for is SQLAlchemy built-in ORM to build forms from models or integrated to database. There are other options to overcome limitations of Flask ORM when needed. Following is the example that would give you some clarity.
from flask import Flask, render_template, redirect, flash
from flask.wtf import Form
from flask.ext.sqlalchemy import SQLAlchemy
from wtf.ext.sqlalchemy.orm import model_form
app=Flask(__app__)
app.config['SECRET_KEY'] = 'secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/employees.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
# Here you initiate the ext
db=SQLAlchemy(app)
#Let's define a model
class Employee(db.Model)
__tablename__ = 'employee'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False
birthday = db.Column(db.Date, nullable=False
def __repr__(self):
return 'employee %s' %self.name
# Now go to your shell inside your env not in gloabal shell outside your env and run this command.
# From your .py file where you've created all above configration first make an import to db from shell
from file.py import db
#Then create a database with following command in shell
db.create.all()
#Your auto generated database based form below
EmployeeForm() = model_form(Employee, base_class=Form, field_args{'name':{'class':'employee'}})
#Let's create a view with model_form or database based form in your case.
#app.route('/', methods=['GET', 'POST'])
def index()
#request.POST does same in Django or any other Python based web framework like Bottle, Tornado etc
form = EmployeeForm()
try:
if form_validate_on_submit():
employee=Employee() #load the model values
form.populate_obj(Employee) #populates the form with respective values
db.session.add(employee) #gathers the session based data to be added in DB
db.session.commit() #Adds data to DB
flash('New Employee added to database successfully.') #Display a message to end user at front end.
retrun redirect('/') # redirects upon success to your homepage.
except Exception e:
# logs the errors
db.session.rollback()
flash('There was a problem registering new employee. Please contact the site administrator at site#site.com')
employee_list = Employe.query.all() #equailent to django style "item.objects.all() to show list of all existing items.
return render_template('index.html', form=form, employee_list=employee_list)
In last line above you did three things. You got your form variable or context variable like you do in Django as "form" so your end user can enter data.
Then you have your model data that is saved in db as "employee_list=employee_list" that will show all the list to end users. "flash" is just like Django
messaging framework.
Now for multiple choices its model has same as djagno choice arguement for key value like below:
With my experience I would suggest you to install "peewee" a simple ORM for Python connected Databases.
choices = (('key', 'value')('key', 'value'))
employee_type = db.Model(db.String(90), choices=('key1', 'key2)
Hope this helps.
Related
I have a request - can you help me access and manage django DB objects without using shell ?
I have created my models, but now (for example) i want to make a login system. I store users and passes(again, only an example), and i want to get the info from the DB, but i dont want to use shell.
What can i do in this case, im quite new to Django ?!
Best Regards
Why not use django-admin?
Maybe this is what you want:https://docs.djangoproject.com/en/3.0/ref/contrib/admin/
In views.py you can import
from .models import modelname
data = modelname.objects.all() - using this you can get all the data from the Database
Eg:-
for d in data:
print (d.email)
Will give all emails in the database
You can also use
t = modelname.objects.get(email='name#lk.com')
By this you can get the data of the person who's email is name#lk.com
Django already has database support where you can register your models and access them with a graphical interface.
See the documentation: django-admin-site
First you need to create a super user account, if you don't have one, create it with the terminal in the project directory, use this row:
python manage.py createsuperuser
For your model to appear on the admin site you need to register it
# models.py
class MyModel(models.Model)
field1 = models.CharField()
field2 = models.TextField()
# ...
# admin.py
from django.contrib import admin
from .models import MyModel
admin.site.register(MyModel)
So it's the basic way to register your model, if you want to personalize you need to check the documentation ModelAdmin.fieldsets
with this done, just access the admin site at the link http://localhost:8000/admin/ and log in with the super user account and you will see the model registered.
I have a working sample Flask-Admin application on GitHub which queries a view (itself based on MySQL's information_schema.TABLES) in order to dynamically update a ModelView's column_labels and column_descriptions properties. There's also a small template modification to add tooltips to the header row containing the column comments from the database.
I put a lot of effort into figuring this out because it seemed foolish to re-type column descriptions in my Python code when they had already been entered into the database using the COMMENT SQL keyword during table creation.
In the simple test app, everything works as expected; I receive the model and session parameters given to the ModelView's __init__ method, and I use the session to query the other model for the column labels / comments, update self.column_labels and self.column_descriptions on the view, then call super().
However, in a more complicated app, I get the dreaded RuntimeError: No application found. Either work inside a view function or push an application context when I try to query the other model within a ModelView's __init__ method. The only marked difference I would note between the demo app and my "real" app is that I import a SQLAlchemy object instantiated in another .py file and then invoke its init_app() in my app.py to wire it up to the Flask instance.
Edit: which is exactly what the problem is; SQLAlchemy instances created using in the usual way, demonstrated in the Flask-SQLAlchemy Quickstart get a proper Flask application instance in their __init__()s; when I use db.init_app(app), I get the No application found error instead.
My question is: at which point does a Flask-Admin ModelView start existing inside a Flask application context? Why are circumstances different for import db from somewhere.py; db.init_app(app) vs. db = SQLAlchemy(app)? Is there any way that I can trace the start-up process of a Flask application and hook into that exact moment so I can see what's going on here?
Here are the two basic parts that are involved (complete source for each is on GitHub, as noted above):
The model that provides the column "metadata," including comments
# models.py
# [SQLALchemy imports and declaration of 'Base']
class ColumnProperty(Base):
# this view is based on MySQL's 'information_schema.TABLES'
__tablename__ = 'v_column_properties'
parent_table = Column(String(64), primary_key=True)
name = Column(String(64), primary_key=True)
# [some fields elided]
comment = Column(String(1024)) # type: str
and
The ModelView which queries the above model upon instantiation
# views.py
from flask_admin.contrib.sqla import ModelView
class TestView(ModelView):
def __init__(self, model, session, **kwargs):
from models import ColumnProperty as cp
descriptions = {}
q = session.query(cp).filter(cp.parent_table==model.__tablename__)
for row in q.all():
descriptions[row.name] = row.comment
self.column_descriptions = descriptions
super(TestView, self).__init__(model, session, **kwargs)
I would override the render method of ModelView and set your column_descriptions there. Obviously you could setup some kind of caching mechanism to reduce the number of database queries.
Something like (completely untested) - note use of self.session in querying:
# views.py
from flask_admin.contrib.sqla import ModelView
class TestView(ModelView):
def render(self, template, **kwargs):
from models import ColumnProperty as cp
descriptions = {}
q = self.session.query(cp).filter(cp.parent_table==model.__tablename__)
for row in q.all():
descriptions[row.name] = row.comment
self.column_descriptions = descriptions
super(TestView, self).render(template, **kwargs)
I realize I'm a bit late to the show but since I had a similar problem I'll share my findings.
It seems that in order to get an application context for the ModelView you have to initialize it within with app.app_context(), like this, in case of your app.py:
...
#app.py
with app.app_context():
admin = Admin(app, name="{}-admin".format(cfg['database']),
index_view=AdminIndexView(name='Admin Home', url='/'),
template_mode='bootstrap3')
admin.add_view(TestView(Test, db.session))
admin.add_view(ColumnPropertiesView(ColumnProperty, db.session))
...
In you prefer using an application factory pattern, you could do something along these lines:
#app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin, AdminIndexView
db = SQLAlchemy()
def create_app():
"""The application factory"""
app = Flask(__name__)
app.config.from_object('app.config')
db.init_app(app)
with app.app_context():
admin = Admin(app, name="{}-admin".format(cfg['database']),
index_view=AdminIndexView(name='Admin Home', url='/'),
template_mode='bootstrap3')
from models import Test, ColumnProperty
from views import TestView, ColumnPropertiesView
admin.add_view(TestView(Test, db.session))
admin.add_view(ColumnPropertiesView(ColumnProperty, db.session))
return app
I have this section of code, which is part of my Flask application. I am using flask_mongoengine.
app = Flask(__name__)
app.config.from_object('config')
db = MongoEngine(app)
from .models import *
#app.context_processor
def inject_config():
return dict(Config.objects.first(), version=version)
Config is a class within .models that extends Document.
class Config(Document):
title = StringField()
description = StringField()
keywords = StringField()
author = StringField()
version = StringField()
meta = {"collection": "web_config"}
Upon calling Config.objects, it's returning an error:
pymongo.errors.OperationFailure: database error: not authorized for query on heroku_dptwtq1j.web_config
I'm logged in through the admin user. Why am I not authorized for query? Also, how do I authorize myself to query?
I have no trouble querying through another application that uses PyMongo, so why is it not working in Flask?
If you still want to use flask_mongoengine there is a work around mentioned in https://github.com/MongoEngine/mongoengine/issues/851 which fixed it for me (though not ideal)
From allanlei:
My work around is actually to patch flask-mongoengine.connection._resolve_settings() to pass the host=mongodb://.... to mongoengine.connect()
Thanks to Chuck from mLabs who helped me find it.
So, answering my own question: the issue was probably in the flask_mongoengine library. I switched to just mongoengine and it worked fine.
Whenever the flask server is started, all the forms are initialised at that time itself. The data is interconnected between the pages, so for one form, the choices come from the database and those choices can be edited using another form on another page but after the choices are updated on that page, they remain the same for the first form. To get the new values I need to restart the server. Is there any way to refresh the values without restarting the server?
This is how the form looks like
class AddExpenses(Form):
reason = wtforms.StringField('reason', [validators.Required()])
amount = wtforms.IntegerField('amount', [validators.Required()])
allnames = []
allnames = getSalesman()
salesperson = wtforms.SelectField('salesperson', choices=[names for names in allnames])
submitfield = wtforms.SubmitField('Submit')
getSalesman() function is used to query the database and get the choices.
Why not get the choices in the view function that requires the form? For example
#app.route('/index')
def index():
form = AddExpenses()
allnames = getSalesman()
form.salesperson.choices = [names for names in allnames]
...
You may also want to use some caching on getSalesman() if the database will not change often.
I'm developing a web-app using Flask and pyMongo, and I've recently started to integrate the Flask-Admin module (1.0.4), given the fresh mongodb support.
All is smooth and fine when using ModelViews, but when it comes to subclassing a BaseView I simply can't get it working.
Here is my setup:
user_view = Admin(app, name='User stuff', url="/user", endpoint="user")
class ProfileForm(wtf.Form):
username = wtf.TextField('Username', [wtf.Required()])
name = wtf.TextField('Name', [wtf.Required()])
class Profile(BaseView):
#expose('/', methods=('GET', 'POST'))
def profile(self):
user = User(uid) # gets the user's data from DB
form = ProfileForm(request.form, obj=user)
if form.validate_on_submit():
data = form.data
user.set(**data)
user.save()
flash("Your profile has been saved")
else:
flash("form did not validate on submit")
return self.render('user/profile.html', form=form, data=user)
user_view.add_view(Profile(name='Profile', url='profile'))
When submitting the form, wtforms does not report any error (unless there is any) but the validation does not return to my profile view (the else: branch is always executed)
There is no way I could find to make this work, inspite having thoroughly scanned flask-admin documentation, source code and examples.
Could anybody suggest how I could fix my code, or work around this problem ?
I have suspicion that form is getting submitted using GET method instead of POST or Flask-WTF CSRF check fails.
Here's small gist I made with your sample code. It works as expected: https://gist.github.com/4556210
Few comments:
Template uses some Flask-Admin library functions to render the form. You don't have to use them if you dont want to;
Uses mock user object
Put template under templates/ subdirectory if you want to run the sample.
In either case, Flask-Admin views behave exactly same way like "normal" Flask views, they're just organised differently.