How to populate wtform select field using mongokit/pymongo? - python

I'm trying to create a SelectField using a mongodb query, but so far I haven't been successful:
# forms.py in blueprint
CATEGORIES = []
for item in db.Terms.find():
CATEGORIES.append((item['slug'], item['name']))
class TermForm(Form):
category = SelectField(
choices=CATEGORIES,
validators=[Optional()])
But I get an exception:
Traceback (most recent call last):
File "/home/one/Projects/proj/manage.py", line 14, in <module>
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
File "/home/one/Projects/proj/app/__init__.py", line 27, in create_app
from app.term.models import Term, TermCategory
File "/home/one/Projects/proj/app/term/__init__.py", line 5, in <module>
from . import views
File "/home/one/Projects/proj/app/term/views.py", line 7, in <module>
from .forms import TermForm, CategoryForm
File "/home/one/Projects/proj/app/term/forms.py", line 48, in <module>
for item in db.Terms.find():
File "/home/one/.venv/proj/lib/python3.4/site-packages/flask_mongokit.py", line 238, in __getattr__
self.connect()
File "/home/one/.venv/proj/lib/python3.4/site-packages/flask_mongokit.py", line 196, in connect
host=ctx.app.config.get('MONGODB_HOST'),
AttributeError: 'NoneType' object has no attribute 'app'
If anyone could shed a little more light upon the subject, I would be very appreciative.

It looks like you're calling a method that needs an app context (db.Terms.find) without having the context available. You can populate the choices in the view instead:
# forms.py
class TermForm(Form):
category = SelectField(validators=[Optional()])
# views.py
form = TermForm()
form.category.choices = [(item['slug'], item['name']) for item in db.Terms.find()]

See the bottom of this answer for what you really want, this first section is just to explain the immediate error you're getting.
You are running code that depends on an application context outside of such a context. You'll need to run the code that populates CATEGORIES inside an application context so that the Flask-MongoKit db can get a connection.
It looks like you're using an application factory, so refactor your code a bit so that you can populate the collection while creating the app (you need access to the app to set up a context).
Put the code inside a function, then import and call that function in the factory within a context.
# forms.py
CATEGORIES = []
def init():
for item in db.Terms.find():
CATEGORIES.append((item['slug'], item['name']))
# add to create_app function (even the import)
def create_app(conf):
#...
from app.term import forms
with app.app_context():
forms.init()
#...
If you you're using blueprints, you can add a function that executes when the blueprint is registered, so the app factory doesn't need to know about all the details. Move all imports such as views to inside this registration function. The changes from above are not needed.
# at the bottom of app/term/__init__.py
# assuming blueprint is called bp
#bp.record_once
def register(state):
with state.app.app_context():
from . import forms, views
However, neither of these are probably what you actually want in this case. It looks like you're trying to dynamically set the choices for a form field to the current values in the database. If you do this as you are now, it will be populated once when the app starts and then never change even if the database entries change. What you really want to do is set the choices in the form's __init__ method.
class TestForm(Form):
category = SelectField()
def __init__(self, *args, **kwargs):
self.category.kwargs['choices'] = [(item['slug'], item['name']) for item in db.Terms.find()]
Form.__init__(self, *args, **kwargs)

Related

How to fix "RuntimeError: Working outside of application context." when creating blueprints with Flask?

I'm trying to create a Blueprint and ran into this problem:
Traceback (most recent call last):
File "C:\Users\Max\PycharmProjects\python1\flask_first\__init__.py", line 3, in <module>
from models import db
File "C:\Users\Max\PycharmProjects\python1\flask_first\models.py", line 5, in <module>
current_app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.sqlite3.html' # access to the SQL
File "C:\python3.9\lib\site-packages\werkzeug\local.py", line 347, in __getattr__
return getattr(self._get_current_object(), name)
File "C:\python3.9\lib\site-packages\werkzeug\local.py", line 306, in _get_current_object
return self.__local()
File "C:\python3.9\lib\site-packages\flask\globals.py", line 52, in _find_app
raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
I've already done a lot of research and nothing works for me (or I'm just not looking properly enough).
This is the models.py code:
from flask_sqlalchemy import SQLAlchemy
from flask import current_app
current_app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.sqlite3.html' # access to the SQL
current_app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(current_app)
class users(db.Model):
__tablename__ = 'users'
_id = db.Column('id', db.Integer, primary_key=True)
name = db.Column(db.String(80))
email = db.Column(db.String(120))
password = db.Column(db.Integer)
def __init__(self, name, email, password):
self.name = name
self.email = email
self.password = password
And this is the __init__.py:
from datetime import timedelta
from flask import Flask
from models import db
from flask_first.admin.second import second
def create_app():
app = Flask(__name__)
with app.app_context():
db.init_app(app)
return app
create_app.secret_key = 'hello world'
create_app.permanent_session_lifetime = timedelta(minutes=5) # setting the time for long-lasting session
if __name__ == '__main__':
db.create_all()
create_app.run(debug=True)
Here is a screenshot of my structure:
Here I'll expand on my comment into an answer.
Python executes your code line-by-line, and that includes import statements. As the error indicates, when it entered __init__.py and got to the from models import db line, it immediately jumped to models.py, and started executing your lines there.
Traceback (most recent call last):
File "...\__init__.py", line 3, in <module>
from models import db
File "...\models.py", line 5, in <module>
current_app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.sqlite3.html'
At this point, the imported current_app does not exist yet, because the create_app from __init__.py seems to have not been called yet. This is where you'll get the common Flask error of "working outside of application context:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
Most of the time you get this type of error, the solution is to reorder your initialization codes. Make sure that the Flask app instance is already created before you do anything else. It should usually be the first thing your code does.
Following the Quickstart tutorial from flask-sqlalchemy, you can put the db object initialization near the app initialization.
# Create app object
app = Flask(__name__)
# Set configs
app.config['...'] = ...
# Create db object
db = SQLAlchemy(app)
The app and db objects typically reside together in some top-level main module. Then, in your other sub-modules (controllers, models, etc.) where you need to setup each component separately, import the app and/or db from the main module:
from some_main_module import app, db
# Do stuff with app and db

How to get data from ModelViewSet in DRF without calling an API call

I'm going to convert all my APIs into gRPC calls. At the moment I was able to transfer all the ViewSet into gRPC(sample code added end of this question). But ModelViewSet, it get an error like this.
Traceback (most recent call last):
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/grpc/_server.py", line 435, in _call_behavior
response_or_iterator = behavior(argument, context)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/servicers/tenant/main.py", line 15, in get_tenant
data = ClientViewSet().list(request=original_request, )
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/common/lib/decorators.py", line 128, in wrapper_format_response
final_data = call_func(func, self, request, transaction, exception, *args, **kwargs)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/common/lib/decorators.py", line 99, in call_func
return func(self, request, *args, **kwargs)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/api_v1/viewsets.py", line 471, in list
data = super().list(request, *args, **kwargs).data
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/rest_framework/mixins.py", line 38, in list
queryset = self.filter_queryset(self.get_queryset())
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/rest_framework/generics.py", line 158, in filter_queryset
queryset = backend().filter_queryset(self.request, queryset, self)
AttributeError: 'ClientViewSet' object has no attribute 'request'
So my viewsets.py look like this (format_response decorator convert it to Response object)
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ser.ClientSerializer
#format_response(exception=False)
def list(self, request, *args, **kwargs):
data = super().list(request, *args, **kwargs).data
# data = {}
return data, True, HTTPStatus.OK, 'data retrieve successfully'
When I call this as an API, it works perfectly. But I want to do the same thing without calling an API. Here how I was solving it,
from django.http import HttpRequest
from rest_framework.request import Request
# creating request object
django_request = HttpRequest()
django_request.method = 'GET'
drf_request = Request(django_request)
data = ClientViewSet().list(request=drf_request)
print(f'data: {data.data}')
The problem with the super() function in the ClientViewSet, but if I uncomment data = {} and comment out the calling super() function, it works both API and the above method. I go through inside the DRF code base where error occurred, when calling the API the self object has the request object and above method it doesn't exist.
In short, you need to call as_view() on the viewset and map the http verbs, and then call that function with a proper HttpRequest.
All views in django end up being functions, DRF is sugar on top of that. A "ViewSet" doesn't exist in the normal way, as a standalone class, after routing is complete.
django_request = HttpRequest()
django_request.method = 'GET'
my_view = ClientViewSet.as_view({'get': 'list', 'post':'create'})
data = my_view(request=django_request)
print(f'data: {data.data}')
If you want the detail routes (users/37, ...), then you need to create a new view function mapping to those viewset functions. This can't be the same view function because http get now needs to point to a different function on the viewset than in the list case. See routers.py source for whats going on and what is mapped where.
# map http 'get' to the 'retrive' function on the viewset
my_view = ClientViewSet.as_view({'get': 'retrieve', ...})
# pass in the kwarg the URL routing would normally extract
client_pk = 9329032
data = my_view(request=django_request, pk=client_pk)
If you want to see what all the mappings for your viewset are, then you an print them out using this snippet:
router = SimpleRouter()
router.register("client", ClientViewSet, basename="client")
for url in router.urls: # type: URLPattern
print(f"{url.pattern} ==> {url.callback}")
for verb, action in url.callback.actions.items():
print(f" {verb} -> {action}")
# output will be something like this
^client/$ ==> <function ClientViewSet at 0x11b91c280>
get -> list
^client/(?P<pk>[^/.]+)/$ ==> <function ClientViewSet at 0x11b91c3a0>
get -> retrieve
put -> update
patch -> partial_update
delete -> destroy
The kwargs you pass to this view will depend on the settings in your ViewSet for things like lookup_url_kwarg, but in most cases they will be simple.

Do I need a manager when creating and saving objects in Django?

I'm using Django with Python 3.7 and PyCharm. I'm following this tutorial for learning how to create model and save them into the database -- https://docs.djangoproject.com/en/dev/topics/db/queries/#creating-objects . The tutorial refers to manager for retrieving objects, but not for setting them, so I'm confused as to why I get the below error
Article.objects.create_article(main page, '/path', 'url', 10, 22)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'create_article'
when I'm trying to create and save an object. Below is how I define my object in my models.py file ...
class Article(models.Model):
mainpage = models.ForeignKey(MainPage, on_delete=models.CASCADE,)
path = models.CharField(max_length=100)
url = models.TextField
time = models.DateTimeField(default=datetime.now)
votes = models.IntegerField(default=0)
comments = models.IntegerField(default=0)
def __str__(self):
return self.path
#classmethod
def create(cls, mainpage, path, url, votes, comments):
article = cls(mainpage=mainpage,path=path,url=url,votes=votes,comments=comments)
return article
I'm sure I'm missing something really obvious, but I don't know what it is.
Edit: Many have suggested using the "Article.objects.create" method but below is the output from the console ...
Article.objects.create(mainpage=mainpage, path='/path', url='url', votes=10, comments=22)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/query.py", line 411, in create
obj = self.model(**kwargs)
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/base.py", line 485, in __init__
raise TypeError("'%s' is an invalid keyword argument for this function" % kwarg)
TypeError: 'url' is an invalid keyword argument for this function
When you define a Model Class, Django jumps into the fray and adds a default Manager to your model under the "objects" property. You can customize the Model and the Manager for different purposes.
Generally methods on your Model should deal with a single instance, or conceptually a row in your database. Model.save(), Model.delete() all act on a single instance or row.
Methods on your manager should usually work with your table as a whole, such as filter(), get(), aggregate(), because these functions perform operations against your table. You also have the create() method on your manager because it adds a row to your table. Further, you can define custom Manager's and assign them to different properties on your model. For example:
class EngineerManager(Manager):
def get_queryset(self):
return super().get_queryset().filter(employee_type="engineer")
class ManagerManager(Manager):
def get_queryset(self):
return super().get_queryset().filter(employee_type="manager")
class Employee(Model):
employee_type = models.CharField()
engineers = EngineerManager()
managers = ManagerManager()
objects = Manager() # This is always added to your models to list all objects
Employee.engineers.filter() # Will only return engineering employees
Employee.objects.filter() # Will return ALL employees
Now to your problem, Article.objects.create_article(...) does not seem to exist, you should probably use Article.objects.create(...) because the default Manager does have a create() method.
Using Article.objects.create(...) will persist a new Article to the database and return it. Otherwise, you could technically use your Article.create(...) method to create an in-memory instance of Article, however it has not been saved to the database so keep in mind you will have to call save() on the instance before it's persisted to your database.
https://docs.djangoproject.com/en/2.1/topics/db/models/
https://docs.djangoproject.com/en/2.1/topics/db/managers/
To create an article in database use manager's create method (params should be named, not positioned, as in your example)
Article.objects.create(mainpage=mainpage, path=path, url=url,
votes=votes, comments=comments)
or you can initialise class instance and then save it. Here params could be positioned or named, no matter.
article = Article(mainpage, path, url, votes, comments)
article.save()
Your method def create(cls, mainpage, path, url, votes, comments): makes no sense, because it duplicates call (__call__ method) of a class, like in my second example. If you want to add some extra logic to object creation, you should define custom Manager class and add method there and then link your model's objects property to you custom manager class like objects = CustomManager()

current_app and importing config settings in Flask

Recently, I've started building an app with Flask. It's pretty easy to use, yet powerful and fun to use. Unfortunately, I'm having problems understanding how the app context works. I know about current_app, app_context and that current_app references are supposed to be used inside requests, but that's probably part of my problem.
I'm building an app with the following folder structure:
app/
main/
__init__.py
error.py
forms.py
routes.py
static/
templates/
__init__.py
email.py
models.py
config.py
run.py
I'm doing an from flask import current_app in routes.py to import config settings and it works as expected.
But when I import current_app in forms.py I can do whatever I want, but I always get this error:
Traceback (most recent call last):
File "./run.py", line 6, in <module>
app = create_app('development')
File "/home/letmecode/Projects/python/flask/folder/app/__init__.py", line 34, in create_app
from main import main as main_blueprint
File "/home/letmecode/Projects/python/flask/folder/app/main/__init__.py", line 5, in <module>
from . import routes, errors, forms
File "/home/letmecode/Projects/python/flask/folder/app/main/routes.py", line 8, in <module>
from .forms import (ChangePasswordForm, ChangeEmailForm, CreateItemForm, RetrievePasswordForm, LoginForm,
File "/home/letmecode/Projects/python/flask/folder/app/main/forms.py", line 132, in <module>
class CreateItemForm(Form):
File "/home/letmecode/Projects/python/flask/folder/app/main/forms.py", line 133, in CreateItemForm
print current_app
File "/home/letmecode/.virtualenvs/folder/local/lib/python2.7/site-packages/werkzeug/local.py", line 362, in <lambda>
__str__ = lambda x: str(x._get_current_object())
File "/home/letmecode/.virtualenvs/folder/local/lib/python2.7/site-packages/werkzeug/local.py", line 302, in _get_current_object
return self.__local()
File "/home/letmecode/.virtualenvs/folder/local/lib/python2.7/site-packages/flask/globals.py", line 34, in _find_app
raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context
forms.py is just a module that contains flask-wtf form classes and validator functions.
My problem is, that I want to reference the app context inside there, in order to get settings that are used throughout the app.
What am I doing wrong here? I guess what happens inside forms.py is not part of a request and therefore fails somehow. But how else am I supposed to reference the app context, then?
Edit:
#forms.py
from flask import current_app
#[...]
class CreateItemForm(Form):
title = TextField('Title', [
validators.Length(min=3, max=140),
validators.Regexp('^[a-zA-Z][a-zA-Z0-9 ]+$', 0, 'Item titles must start with a letter and consist of numbers and letters, only')
])
description = TextAreaField('Description', [
validators.Length(min=3, max=1000),
])
tags = TextField('Tags')
branch = SelectField(
'Branch',
choices=current_app.config['BRANCHES']
)
The underlying issue is that CreateItemForm and all of the attributes are created when the forms module is imported for the first time. That means that the branch field is created whenever import .form is run, and therefore current_app is accessed then:
# app/main/routes.py
from .forms import ( ..., CreateItemForm, ...)
Looking at your stack trace, this happens in your create_app call - most likely, your create_app looks something like this:
def create_app(config_name):
app = Flask('some-name-here')
app.config.from_SOMETHING(config_name)
# Register things here
from main import main as main_blueprint
app.register_blueprint(...)
return app
At # Register things here you can simply create an app_context and import everything inside of that:
# Register things here
# Creating an explicit application context to allow
# easy access to current_app.config, etc.
with app.app_context():
from main import main as main_blueprint
app.register_blueprint(...)
return app
I found that I was being an idiot. You don't have to provide choices per form model. You can (and should) provide them via the view/route function e.g.:
form.select_field.choices = [('value1', 'label1'), ('value2', 'label2')]
This doesn't explicitly answer my question, but renders it somewhat obsolete. However, I won't accept my own answer as a solution (yet), because I am curious as to whether it's at all possible to import configuration options from via app/current_app in this case, without getting hacky. If my curiosity won't be satisfied, I'll accept my own answer.
I wish the wtforms/flask-wtf documentation would focus less on the obvious and more on the not so obvious.

Flask-Admin Blueprint creation during Testing

I'm having trouble with the creation of blueprints by Flask-Admin when I'm testing my app.
This is my View class (using SQLAlchemy)
##
# All views that only admins are allowed to see should inherit from this class.
#
class AuthView(ModelView):
def is_accessible(self):
return current_user.is_admin()
class UserView(AuthView):
column_list = ('name', 'email', 'role_code')
This is how I initialize the views:
# flask-admin
admin.add_view(UserView(User, db.session))
admin.init_app(app)
However, when I try to run more then one test (the fault always occurs on the second test and all the other tests that follow), I always get following error message:
======================================================================
ERROR: test_send_email (tests.test_views.TestUser)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/lib/python2.7/site-packages/nose/case.py", line 133, in run
self.runTest(result)
File "/lib/python2.7/site-packages/nose/case.py", line 151, in runTest
test(result)
File "/lib/python2.7/site-packages/flask_testing.py", line 72, in __call__
self._pre_setup()
File "/lib/python2.7/site-packages/flask_testing.py", line 80, in _pre_setup
self.app = self.create_app()
File "/tests/test_init.py", line 27, in create_app
app = create_app(TestConfig)
File "/fbone/app.py", line 41, in create_app
configure_extensions(app)
File "/fbone/app.py", line 98, in configure_extensions
admin.add_view(UserView(User, db.session))
File "/lib/python2.7/site-packages/flask_admin/base.py", line 484, in add_view
self.app.register_blueprint(view.create_blueprint(self))
File "/lib/python2.7/site-packages/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/lib/python2.7/site-packages/flask/app.py", line 885, in register_blueprint
(blueprint, self.blueprints[blueprint.name], blueprint.name)
AssertionError: A blueprint's name collision occurred between <flask.blueprints.Blueprint object at 0x110576910> and <flask.blueprints.Blueprint object at 0x1103bd3d0>. Both share the same name "userview". Blueprints that are created on the fly need unique names.
The strange thing is that this only happens on the second test and never when I just run the app.
When I debugged the tests, the first time it did exactly what I expected and added the blueprint to the app after the init_app(app). The second time however the process immediately stopped when reaching the add_view step (which I think is strange because the blueprints get registered in the init_app(app) call?)
The same thing happened to me while using Flask-Admin and testing with pytest. I was able to fix it without creating teardown functions for my tests by moving the creation of the admin instance into the app factory.
Before:
# extensions.py
from flask.ext.admin import Admin
admin = Admin()
# __init__.py
from .extensions import admin
def create_app():
app = Flask('flask_app')
admin.add_view(sqla.ModelView(models.User, db.session))
admin.init_app(app)
return app
After:
# __init__.py
from flask.ext.admin import Admin
def create_app():
app = Flask('flask_app')
admin = Admin()
admin.add_view(sqla.ModelView(models.User, db.session))
admin.init_app(app)
return app
Because pytest runs the app factory each time it no longer tries to register multiple views on a global admin instance. This isn't consistent with typical Flask extension usage, but it works and it'll keep your app factory from stumbling over Flask-Admin views.
I had to add the following to my test case tearDown. It cleans up the views that were added to the admin extension in the test setup
from flask.ext.testing import TestCase
from flask.ext.admin import BaseView
# My application wide instance of the Admin manager
from myapp.extensions import admin
class TestView(BaseView):
...
class MyTestCase(TestCase):
def setUp(self):
admin.add_view(TestView())
def tearDown(self):
admin._views.pop(-1)
admin._menu.pop(-1)
This is certainly a bit of a hack, but it got the job done while I had this problem.
Just in case this helps anyone,
another way to handle this is to do:
class MyTestCase(TestCase):
def setUp(self):
admin._views = []
this way you don't have to set the Admin() initialization inside the factory. It seems more appropiate to me.
It work out in this way. just for your reference.
#YourApp/init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
db = SQLAlchemy()
admin = Admin(name='TuozhanOA', template_mode='bootstrap3')
def create_app(config_class=Config):
app = Flask(name)
app.config.from_object(Config)
db.init_app(app)
admin.init_app(app)
from YourApp.main.routes import main
app.register_blueprint(main)
from YourApp.adminbp.routes import adminbp, user_datastore
app.register_blueprint(adminbp)
security = Security(app, user_datastore)
return app
#YourApp/adminbp/routes.py
from flask import render_template, Blueprint
from YourApp.models import User, Role
from YourApp import db, admin
from flask_admin.contrib.sqla import ModelView
from wtforms.fields import PasswordField
from flask_admin.contrib.fileadmin import FileAdmin
import os.path as op
from flask_security import current_user, login_required, RoleMixin, Security,
SQLAlchemyUserDatastore, UserMixin, utils
adminbp = Blueprint('adminbp', name)
admin.add_view(ModelView(User, db.session, category="Team"))
admin.add_view(ModelView(Role, db.session, category="Team"))
path = op.join(op.dirname(file), 'tuozhan')
admin.add_view(FileAdmin(path, '/static/tuozhan/', name='File Explore'))

Categories