SQLAlchemy Query foreignkey field(s) - python

I am trying to write a view/SQLAlchemy query that will allow me to display data thats linked as a ForeignKey and also where I have a ManyToMany relationship
have the models..
#Samples
class Sample(Base):
__tablename__ = 'samples'
id = Column(Integer, primary_key=True)
name = Column(Unicode)
def __unicode__(self):
return self.name
samples_to_customer = Table('samples_to_customer', Base.metadata,
Column('customer_id', Integer, ForeignKey('customer.id')),
Column('sample_id', Integer, ForeignKey('samples.id'))
)
#Create a cusotmer model to store customer numbers
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(Unicode, unique=True) #this will be the variable used to search the existing db
customer_samples = relationship('Sample', secondary='samples_to_customer', backref='samples')
sales_rep = Column(Integer, ForeignKey('rep.id'))
Rep = relationship('Rep')
def __unicode__(self):
return self.name
# This model will have its own entry view/page a user logs on and enters notes (according to a customer
# number) they then get stored/saved onto the Customers account
class Note(Base):
__tablename__ = 'note'
id = Column(Integer, primary_key=True)
name = Column(Unicode)
pub_date = Column(Date)
customer_no = Column(Integer, ForeignKey('customer.id'))
customer = relationship('Customer')
def __unicode__(self):
return self.name
And the views..
#view_config(route_name='view_customer', renderer='templates/view_customer.jinja2')
def view_page(request):
customer_no = request.matchdict['customer']
cust_slcustm = DBSessionRO.query(Slcustm).filter(Slcustm.customer == customer_no).first()
cust_customer = DBSessionRO.query(Custom).filter(Custom.cu_custref== customer_no).first()
# Return a 404 if a result isn't found, slcustm and customer share one2one relationship on customer_no
if cust_slcustm is None:
return HTTPNotFound('No such customer')
return dict(cust_slcustm=cust_slcustm, cust_customer=cust_customer)
But I cant seem to write a query that will display every sample and note related to a customer number? If anyone could help I would be very grateful :)

Try this
class Note(Base):
__tablename__ = 'note'
id = Column(Integer, primary_key=True)
name = Column(Unicode)
pub_date = Column(Date)
customer_no = Column(Integer, ForeignKey('customer.id'))
customer = relationship('Customer', backref='notes')
#view_config(route_name='view_customer', renderer='templates/view_customer.jinja2')
def view_page(request):
customer_no = request.matchdict['customer']
cust = DBSessionRO.query(Customer).filter(Customer.id == customer_no).first()
print "Sample :", cust.customer_sample
print "Notes :", cust.notes

What ended up working was:
#views.py
#view_config(route_name='view_customer', renderer='templates/view_customer.jinja2')
def view_page(request):
customer_no = request.matchdict['customer']
cust_slcustm = DBSessionRO.query(Slcustm).filter(Slcustm.customer == customer_no).first()
cust_customer = DBSessionRO.query(Custom).filter(Custom.cu_custref== customer_no).first()
cust_books = DBSessionRW.query(Customer).filter(Customer.name == customer_no).first()
# Return a 404 if a result isn't found, slcustm and customer share one2one relationship on customer_no
if cust_slcustm is None:
return HTTPNotFound('No such customer')
return dict(cust_slcustm=cust_slcustm, cust_customer=cust_customer, cust_books=cust_books)
Then creating the template:
{% for sample in cust_books.customer_samples %}
{{ sample.name }}
{% endfor %}

Related

How to determine if a user is a member of an admin group in Flask

I'm new to using Flask, so apologies for what might be a basic question.
I'm working on a new Flask application that has multiple tables in a database. There is a table for storing the users information (name, password, enabled). Another table holds the group names and if it is an admin group (type is boolean). A third table relates the user to the group(s) they are a member of, since they could be a member of more than one. Here is the relevant code from models.py
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(10), index=True, unique=True)
password_hash = db.Column(db.String(128))
active_user = db.Column(db.Boolean)
group_ids = db.relationship('GroupMembers', backref='user', lazy='dynamic' )
def __repr__(self):
return 'User {} | Active {}'.format(self.username, self.active_user)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def check_admin(self):
group_ids = self.group_ids.all()
for row in group_ids:
group = Group.query.get(int(row.gid))
if group.admin_rights:
return True
return False
class Group(db.Model):
gid = db.Column(db.Integer, primary_key=True)
groupname = db.Column(db.String(60), index=True, unique=True)
admin_rights = db.Column(db.Boolean)
group_members = db.relationship('GroupMembers', backref='members', lazy='dynamic')
def __repr__(self):
return 'ID: {} | Name: {} | Admin: {}'.format(self.gid, self.groupname, self.admin_rights)
def is_admin_group(self, gid):
_group = self.query.get(int(gid))
return _group.admin_rights
class GroupMembers(db.Model):
__table_args__ = ( db.UniqueConstraint('gid', 'uid'), )
id = db.Column(db.Integer, prmary_key=True)
gid = db.Column(db.Integer, primary_key=True, db.ForeignKey('group.gid'))
uid = db.Column(db.Integer, primary_key=True, db.ForeignKey('user.id'))
def __repr__(self):
return "{}.{}".format(self.gid, self.uid)
I'm trying to find the best way to determine if a user is a member of a admin group (group.admin_rights == True). The 'check_admin' function in 'User' works, but I'm unsure of how to call this from within the html templates. Could this be called using something like
{% if current_user.check_admin() %}
assuming that your check_admin() is working fine, you can pass the additional check_admin flag like this
return render_template('sample_page.html', admin=check_admin())
and in the Jinja template, you can simply check for an admin user with:
{% if admin %}

Flask-Admin: Change sort order of inline_models?

In Flask-Admin, is there any way to control the order of the list generated by inline_models? It seems to be coming out in database order, i.e. ordered by the ID primary key.
That is, if I have an Author that has_many Books, and my AuthorModelView class has inline_models = (Books,), the books are always ordered by book_id. Passing column_default_sort to the inline model, to try to sort by (say) title or date_purchased, has no effect. Is there any way to handle this?
Specify the order_by parameter when specifying the relationships, see docs. See note at the end if you want to sort by a specific field at runtime.
Example of model declarations for Author -> Books. Here we are ordering on the book title field ascending - order_by='Book.title.asc()' :
class Author(db.Model):
__tablename__ = 'authors'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Text(length=255), nullable=False)
last_name = db.Column(db.Text(length=255), nullable=False)
books = db.relationship("Book", order_by='Book.title.asc()', cascade="all,delete-orphan", backref=db.backref('author'))
def __str__(self):
return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('authors.id'), nullable=False, index=True)
title = db.Column(db.Text(length=255), nullable=False)
def __str__(self):
return f"ID: {self.id}; Title: {self.title}; Author ID: {self.author_id}"
Single file full example:
from faker import Faker
import click
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib import sqla
db = SQLAlchemy()
class Author(db.Model):
__tablename__ = 'authors'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.Text(length=255), nullable=False)
last_name = db.Column(db.Text(length=255), nullable=False)
books = db.relationship("Book", order_by='Book.title.asc()', cascade="all,delete-orphan", backref=db.backref('author'))
def __str__(self):
return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('authors.id'), nullable=False, index=True)
title = db.Column(db.Text(length=255), nullable=False)
def __str__(self):
return f"ID: {self.id}; Title: {self.title}; Author ID: {self.author_id}"
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_ECHO'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample.sqlite'
db.init_app(app)
#app.cli.command('create-database', short_help='Create Authors database')
#click.option('--count', default=100, help='Number of authors (default 100)')
def create_database(count):
"""
Create database with "count" authors
"""
db.drop_all()
db.create_all()
_faker = Faker()
for _ in range(0, count):
_author = Author(
first_name=_faker.first_name(),
last_name=_faker.last_name(),
)
db.session.add(_author)
for _ in range(0, _faker.pyint(1, 20)):
_book = Book(
title=_faker.sentence(),
author=_author
)
db.session.add(_book)
db.session.commit()
class AuthorView(sqla.ModelView):
# default sort: last_name ascending
column_default_sort = ('last_name', False)
inline_models = (Book,)
# Flask views
#app.route('/')
def index():
return 'Click me to get to Admin!'
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(AuthorView(Author, db.session))
if __name__ == '__main__':
app.run()
Run the following command to initialize an SQLite DB.
flask create-database --count 100
If you want to change the sort field at runtime override the view's get_one() method and use Python to sort the instrumented list directly. For example, sorting by ISBN field instead of title:
class Author2View(sqla.ModelView):
def get_one(self, id):
_author = super().get_one(id)
_author.books = sorted(_author.books, key=lambda book: book.isbn)
return _author
# default sort: last_name ascending
column_default_sort = ('last_name', False)
inline_models = (Book,)
admin.add_view(Author2View(Author, db.session, name="Author 2", endpoint='author-2'))

Filter out data from sqlalchemy dynamically

I have these models
class Integration(Base, ModelBase, ModelSerializer):
__tablename__ = 'integration'
id = Column(Integer, primary_key=True)
domain = relationship('Domain', backref='integration')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
meta = Column(JSON)
tag = Column(String(512))
identifier_id = Column(String(512), unique=True, index=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Domain(Base, ModelBase, ModelSerializer):
__tablename__ = 'domain'
id = Column(Integer, primary_key=True)
integration_id = Column(Integer, ForeignKey('integration.id'), index=True)
url = relationship('Url', backref='domain')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
domain = Column(String(512), nullable=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Url(Base, ModelBase, ModelSerializer):
__tablename__ = 'url'
id = Column(Integer, primary_key=True)
domain_id = Column(Integer, ForeignKey('domain.id'), index=True)
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
url = Column(String(512), nullable=True)
meta = Column(JSON)
auth = Column(JSON)
def __repr__(self):
return '<name {}>'.format(self.name)
class ModelBase(object):
def __repr__(self):
return '<id {}>'.format(self.id)
class ModelSerializer(object):
def as_dict(self):
return {attribute.name: getattr(self, attribute.name) for attribute in self.__table__.columns}
Now I need to filter out the data based on the filters.
If suppose integration_filters are given then only filter the Integration data.
If Integration and Domain are given then filter only domain and integration by joining them
Same with Url and Domain
Solutions I tried -
1). Doesnt work as it doesnt join then tables. Later figured out that add_entity doesnt add the models.
if integration_filter:
query = query.add_entity(Integration)
query = query.filter_by(**kwargs['integration_filter'])
if domain_filter:
query = query.add_entity(Domain)
query = query.filter_by(**kwargs['domain_filter'])
if url_filter:
query = query.add_entity(Url)
query = query.filter_by(**kwargs['url_filter'])
2). It checks for the attribute 'domain' in Integration table
if integration_filter:
query = session.query(Integration).filter_by(**integration_filter)
if domain_filter:
if query:
query = query.join(Domain)
else:
query = session.query(Domain)
query = query.filter_by(**domain_filter)
if url_filter:
if query:
query = query.join(Url)
else:
query = session.query(Url)
query = query.filter_by(**url_filter)`
3). This doesnt work either
models = []
joins = []
if integration_filter:
models.append(Integration)
if domain_filter:
if models:
joins.append((Integration, Domain, Integration.id == Domain.integration_id))
models.append(Domain)
if url_filter:
if models:
joins.append((Domain, Url, Domain.id == Url.domain_id))
models.append(Url)
query = session.query(*models)
for join in joins:
query = query.join(*join)
This one worked. Was pretty simple though, didn't think of it in the beginning
try:
session = Backend().get_session()
models = []
join_filter = []
if integration_filter:
models.append(Integration)
join_filter.append({'filter': integration_filter})
if domain_filter:
models.append(Domain)
join_filter.append({'join': (Domain, Domain.integration_id == Integration.id), 'filter': domain_filter})
if url_filter:
models.append(Url)
join_filter.append({'join': (Url, Url.domain_id == Domain.id), 'filter': url_filter})
query = session.query(*models)
for jf in join_filter:
if 'join' in jf:
query = query.join(jf['join'])
if 'filter' in jf:
query = query.filter_by(**jf['filter'])
query = query.__getattribute__(query_type)
self.records = query()
except InvalidRequestError as ir:
raise Exception("Invalid query in URL Builder Error")
except NoResultFound as nrf:
raise Exception(
"No URL was found for filters integration - {} domain - {} url - {}".format(integration_filter,
domain_filter, url_filter))
except MultipleResultsFound as mrf:
raise Exception(
"Multiple Results was found for filters integration - {} domain - {} url - {}".format(
integration_filter, domain_filter, url_filter))
except Exception as e:
raise Exception("Error while fetching records in URL Builder {}")
finally:
session.close()

Flask - WTForm - Get Form Data as NoneType as opposed to String

I have been searching the internet for a while now in order to solve this issue.
I am working on a proof of concept, a simple input output data entry application.
However, the Form I am using, won't output my NoneType values as I am trying to use filters in order to do so, as illustrated in this stackoverflow thread : Get None from a Fields data in instead of an empty string
However the results are not as expected, as I still only receive Strings.
My Form
class MyForm(Form):
event_date = DateField('Event Date:', format='%Y-%m-%d', validators=[DataRequired()], filters=[lambda x: x or None])
person = StringField('Person', filters=[lambda x: x or None])
office = QuerySelectField(query_factory=get_all_offices, allow_blank=True,
filters=[lambda x: x or None])
event_type = QuerySelectField(query_factory=get_all_event_types, allow_blank=True, filters=[lambda x: x or None])
submit = SubmitField('Search')
Render
#app.route('/myform', methods=['GET', 'POST'])
def myform():
if request.method == 'GET':
form = MyForm()
return render_template('my_form.html', form=form)
else:
print(request.form)
context = {}
event_date = request.form['event_date']
office = request.form['office']
person = request.form['person']
event_type = request.form['event_type']
print(person)
print(event_date)
print(type(office))
print(event_type)
The Input
The Output
As you can see, office, and event type are coming through as a String Object, with a value of "__None".
Any ideas if it is possible to have the form handle the translation of '' or '__None' to the builtin NoneType ?
Edit: Schema for person / office
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
political_party = Column(String(64), nullable=False)
ap_candidate_id = Column(String(64), nullable=False)
def __repr__(self):
return self.name
class Office(Base):
__tablename__ = 'offices'
id = Column(Integer, primary_key=True, autoincrement=True)
desc = Column(String(64), nullable=False)
def __repr__(self):
return self.desc
class Event(Base):
__tablename__ = 'events'
id = Column(Integer, primary_key=True, autoincrement=True)
event_date = Column(DateTime, nullable=False)
office_id = Column(Integer, ForeignKey(Office.id))
office = relationship('Office', backref='events')
winner_id = Column(Integer, ForeignKey(Person.id))
winner = relationship('Person', foreign_keys=winner_id, backref='events_won')
loser_id = Column(Integer, ForeignKey(Person.id))
loser = relationship('Person', foreign_keys=loser_id, backref='events_lost')
event_type_id = Column(Integer, ForeignKey(EventType.id))
event_type = relationship('EventType', backref='events')
state_id = Column(Integer, ForeignKey(State.id))
state = relationship('State', backref='state_events')
def __repr__(self):
return '{0} - {1}'.format(self.office, self.event_date)
You are doing
print request.form['event_date']
so basically you are just printing data posted by the browser. You are not even using the MyForm class you created. You need to pass your request.form data to your MyForm class so that it may parse it. And later use your MyForm class instance.
You need to do something like
def myform():
if request.method == 'GET':
form = MyForm()
return render_template('my_form.html', form=form)
else:
form = MyForm(request.form)
if form.validate():
print form.event_date.data
print form.office.data
print form.person.data
print form.event_type.data
For more details, please go through docs about how to handle forms in views.
Your filters need to take into account that the value could be the string value "__None", currently your filters are just checking whether the value is absent or not. Something like the following should work (untested).
person = StringField('Person', filters=[lambda x: x or None, lambda x: None if x == '__NONE' else x])

SqlAlchemy Database Issue

I created a Table a Bmarks which has two foreign keys which have relation with same table Url_hash
class Hashed(Base):
__tablename__ = "url_hash"
hash_id = Column(Unicode(22), primary_key=True)
url = Column(UnicodeText)
clicks = Column(Integer, default=0)
def __init__(self, url):
cleaned_url = str(unidecode(url))
self.hash_id = unicode(generate_hash(cleaned_url))
self.url = url
class Bmark(Base):
__tablename__ = "bmarks"
bid = Column(Integer, autoincrement=True, primary_key=True)
hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
clean_hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
description = Column(UnicodeText())
extended = Column(UnicodeText())
stored = Column(DateTime, default=datetime.utcnow)
updated = Column(DateTime, onupdate=datetime.utcnow)
clicks = Column(Integer, default=0)
inserted_by = Column(Unicode(255))
username = Column(Unicode(255), ForeignKey('users.username'),
nullable=False,)
tag_str = Column(UnicodeText())
hashed = relation(Hashed,
foreign_keys="Bmark.hash_id",
backref="bmark",
uselist=False
)
clean_hashed = relation(Hashed,
foreign_keys="Bmark.clean_hash_id",
backref="bmark",
uselist=False
)
I am trying to store url after cleaning it a little bit like removing headers,utm parameters etc for indexing purposes
Error is occurring while creating the database
sqlalchemy.exc.ArgumentError: Error creating backref 'bmark' on relationship 'Bmark.clean_hashed': property of that name exists on mapper 'Mapper|Hashed|url_hash'
Actually the error message is very informative.
Just rename one of your backref="bmark" to something else like backref="my_clean_bmark".

Categories