flask app builder user should see only own content - python

Hi i want that the user is only seeing it's own content which was created by themself. In flask i created a user table and every other table as a reference to the table. So when the view is called i filter for the current user and only show the entries for the user. I now checked out flask app builder and it has some nice user management but it seems that it has nothing like i need.
My solution would be: Create a reference from my table to the user table and do it like i did it with plain flask. I am just wondering if there is a better way to do this and maybe there is allready something in appbuilder what i have to activate but don't see yet.
my flask solution:
this is what i add to the model
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
this is how i query it in the routes
articles_pos = ArticlePos.query.filter_by(user_id=current_user.id)
thanks in advance

A solution can be found in the examples of fab itself. see https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/extendsecurity
in my particular case i made the following changes
in the model i adde the follwoing:
class MyUser(User):
__tablename__ = "ab_user"
and in the class where i reference the user table
user = relationship("MyUser")
user_id = Column(Integer, ForeignKey('ab_user.id'), default=get_user_id, nullable=False)
you still need the the function:
#classmethod
def get_user_id(cls):
try:
return g.user.id
except Exception:
return None

Related

How to store a value as a global variable that is specific to the user?

I am currently working on a small flask app that will be connecting to an api and processing data pulled from that api.
Users are able to login to my flask app and then also define their credentials to interact with the api. Any given user may have one or more API credentials associated with their user.
I've created db models to store user and Api credentials in the database as follows.
I'm using the flask-login module which has the "current_user" object which provides me with the User model of the user that is currently logged in across my entire app.
Models:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
firstname = db.Column(db.String(55))
lastname = db.Column(db.String(55))
password = db.Column(db.String(128))
creds = db.relationship('ApiCredential', backref='owner', lazy='dynamic')
class ApiCredential(db.Model):
__tablename__ = 'api_credentials'
id = db.Column(db.Integer, primary_key=True)
site = db.Column(db.String(140))
user = db.Column(db.String(140))
username = db.Column(db.String(100), index=True, unique=True)
password = db.Column(db.String(55))
base = db.Column(db.String(100))
owner_id = db.Column(db.Integer, db.ForeignKey('users.id'))
active = db.Column(db.Boolean)
I would like to know how to create a similar "global variable" for my API credentials that is specific only to the logged in user and not to all users of the application
NOTE*** It seems as though "current_user" is something called a local proxy which i am not at all familiar with and cannot seem to find any decent documentation or explanation of what it is and how to use it for my needs.
You're in for a fun ride, at the end of which you might choose to do something less magic.
First, it helps to understand how current_user works. The source (as of this moment) is here. It's a werkzeug.local.LocalProxy, which wraps a lambda that calls flask_login.utils._get_user.
LocalProxy is pretty cool, and understanding it is a great way to level-up on Python, but flask-login uses a thin slice of it. The source (as of this moment) is here. It looks scary, but if you trace the code, it merely invokes the local arg (the lambda from flask-login).
That gets us back to _get_user (here, as of this moment), which loads a user if there isn't one (in the top of the current request context), and then returns the user from the top of the request context.
You could follow that pattern, and write a package that exports current_credentials. You'd follow the same pattern, using a werkzeug.local.LocalProxy to wrap a lambda that invoked, say, _get_credentials. The trick would be to import current_user from flask-login, using it with _get_credentials to get to user with which to construct the query to join to your ApiCredentials table.
Or you could take a simple approach, and write utility method for your views to use, which would use current_user to get the user and then do the join to get API credentials for that user.
One method could be to create a new route in your flask app, when the user get requests the page then you can check which user it is, once you know which user it is, you can query using your api credentials model and filter by current_user.id.
creds = ApiCredentials.query.filter_by(owner_id=current_user.id).first_or_404()
Then you can do as you please with the information stored in your API Credentials table.
I don't see why you would want to replicate the user loader functionality. This is the function you will have included at some point when you set up Flask login. Very short function that returns the current user object from the user model.
You could display or return your api keys. Display them on a page as HTML or for more autonomous jobs, you could use jsonify to return the keys as a json string.
I'm sorry this doesn't directly answer your question, but I hope my suggestion might lead you to a slightly less complex answer and you can continue developing your web app. Perhaps it would be worth revisiting at a later date.
Note: this is off the top of my head. The code line I provided might need to be as follows
creds = ApiCredentials.query.filter_by(owner_id==current_user.id).first()
Furthermore, you may not want to use .first() if they have multiple api credentials stored in the table.
In which case this would be more suitable:
creds = ApiCredentials.query.filter_by(owner_id==current_user.id).all()

Database based forms in Flask

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.

Heroku mLab MongoDB admin user not authorized for query in Flask application

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.

Update many-to-many relationships using Flask, SQLAlchemy and WTForms?

I want to create a form for many-to-many relations using Flask, SQLAlchemy and WTForms that represents these models:
personaddress = db.Table('personaddress',
db.Column('person', db.Integer, db.ForeignKey('person.id')),
db.Column('address', db.Integer, db.ForeignKey('address.id'))
)
class Person(db.Model):
__tablename__ = "person"
id = db.Column(Integer, primary_key=True)
name = db.Column(String, nullable=False)
addresses = db.relationship('Address', secondary=personaddress, backref=db.backref('person', lazy='dynamic'))
class Address(db.Model):
__tablename__ = "address"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
Requirements
Now I want to create a single page that contains forms to achieve the following:
add/edit/delete a person
add/edit/delete a address
add/edit/delete a relation between a person and an address
Important requirement: Using QuerySelectField, I can choose existing addresses for a person. But I want to add new addresses in the same form.
I've played around with model_form for the main models and subforms using FormField for the junction table but I just can't figure out how to update everything including the foreign key relations. The page should have a single submit button for all forms and subforms displayed.
Questions
How are the above requirements typically implemented in Flask?
Is this many-to-many scenario something that Django can handle more easily through its admin interface?
I have also encountered something similar earlier. I tried to solve it by using model_form, but it doesn't quite solve the problem of adding new entries dynamically, and I was having a hard time using it when dealing with relations.
Using the QuerySelectField in WTForms will only help you populating eg. an < select > with id, value pairs corresponding to the existing addresses. But it still renders to a regular html form in the template.
By using some sort of multiselect with the possibility to dynamically add new options in the frontend you can send additional addresses in the same form. The the endpoint will take care of creating new Addresses if they don't exist in the db.
The WTForm form would be:
from app import db
class PersonAddressForm(Form):
id = HiddenField('id')
name = StringField('Name')
addresses = QuerySelectField('Addresses',
query_factory=lambda: db.session.query(Address),
get_pk=lambda a: a.id, get_label=lambda a: a.name)
# Custom validate
def validate(self):
# ... custom validation
return True
And the route something like:
# ... this will be used to create and update a user
#route('create/<userid>', methods=["GET"])
def get_user_form(userid):
# ... Get the Person
user = Person()
if userid:
# ... if userid supplied, use existing Person object
user = Person.query.get(userid)
# ... Populate the form
person_form = PersonAddressForm(obj=user)
# ... return form
return render_template('somepage.html', form=person_form)
#route('create/<userid>', methods=["POST"])
def post_person_form(userid):
person_form = PersonAddressForm(request.form)
if person_form.validate():
# ... Get db object
person = db.session.query(Person).get(form.id)
# ... Add changes to the object from the form
person_form.populate_obj(obj=person_address)
# ... Get addresses
addresses = form.addresses.raw_data
# ... loop over and add to person
for address in addresses:
# Add or create an address
actual_address = db.session.query(Address).get(address.id)
# ... check if address is existing
if not actual_address:
# ... if address not existing, create new one
actual_address = Address(address.name)
db.session.add(actual_address)
# ... Append new or created address to person
person.addresses.append(actual_address)
# ... save changes to the db
db.session.commit()
# ... Update/Create complete
return redirect(url_for('get_users'))
else:
# ... form not valid, notify user
# ...
This will handle edit/create user and create Address. As well as create the relation between. To make it also support delete Address, change
person.addresses.append(actual_address)
to
person.addresses = list_of_actual_addresses
and change this in the person model (cascade='delete-orphan')
addresses = db.relationship('Address', secondary=personaddress, cascade='delete-orphan' backref=db.backref('person', lazy='dynamic'))
This will make the form update the entire address relation each time and the cascade will delete orphaned addresses. So the entire addresses list for a person would be updated each time the form is submitted.
When dealing with WTForms in templates i highly recommend using macros if you don't already. You would have to rewrite it to some degree, but check this out.
Hope this helps

SqlAlchemy add tables versioning to existing tables

Imagine that I have one table in my project with some rows in it.
For example:
# -*- coding: utf-8 -*-
import sqlalchemy as sa
from app import db
class Article(db.Model):
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(sa.Unicode(255))
content = sa.Column(sa.UnicodeText)
I'm using Flask-SQLAlchemy, so db.session is scoped session object.
I saw in https://github.com/zzzeek/sqlalchemy/blob/master/examples/versioned_history/history_meta.py
but i can't understand how to use it with my existing tables and anymore how to start it. (I get ArgumentError: Session event listen on a scoped_session requires that its creation callable is associated with the Session class. error when I pass db.session in versioned_session func)
From versioning I need the following:
1) query for old versions of object
2) query old versions by date range when they changed
3) revert old state to existing object
4) add additional info to history table when version is creating (for example editor user_id, date_edit, remote_ip)
Please, tell me what are the best practicies for my case and if you can add a little working example for it.
You can work around that error by attaching the event handler to the SignallingSession class[1] instead of the created session object:
from flask.ext.sqlalchemy import SignallingSession
from history_meta import versioned_session, Versioned
# Create your Flask app...
versioned_session(SignallingSession)
db = SQLAlchemy(app)
class Article(Versioned, db.Model):
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(sa.Unicode(255))
content = sa.Column(sa.UnicodeText)
The sample code creates parallel tables with a _history suffix and an additional changed datetime column. Querying for old versions is just a matter of looking in that table.
For managing the extra fields, I would put them on your main table, and they'll automatically be kept track of in the history table.
[1] Note, if you override SQLAlchemy.create_session() to use a different session class, you should adjust the class you pass to versioned_session.
I think the problem is you're running into this bug: https://github.com/mitsuhiko/flask-sqlalchemy/issues/182
One workaround would be to stop using flask-sqlalchemy and configure sqlalchemy yourself.

Categories

Resources