I'm building a relatively simple Flask application with a not so simple database. I've a lot of models to handle and relate all the needed information in the DB.
For some reason I'm getting the title error.
I'm starting to think is some how related with my DB models/relationships.
So, the implementation of the models goes as the follow:
from app import db, login
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
import datetime
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password = db.Column(db.String(128))
def set_password(self, password):
self.password = generate_password_hash(password)
def check_password(self, passwd):
return check_password_hash(self.password, passwd)
def __repr__(self):
return '<User {}>'.format(self.username)
class airFeedVariator(db.Model):
variatorID = db.Column(db.Integer, db.ForeignKey('variator.id',onupdate='RESTRICT',ondelete='RESTRICT'), primary_key=True)
airFeedID = db.Column(db.Integer, db.ForeignKey('air_feed.id',onupdate='RESTRICT',ondelete='RESTRICT'), primary_key=True)
variatorRel = db.relationship('Variator', backref='variators',lazy='joined')
freq = db.Column(db.Integer)
class Variator(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30))
machineID = db.Column(db.Integer)
baudrate = db.Column(db.Integer)
addedAt = db.Column(db.DateTime, default=datetime.datetime.utcnow) # the current timestamp
class Air_feed(db.Model):
id = db.Column(db.Integer, primary_key=True)
timeOn = db.Column(db.Integer)
timeOff = db.Column(db.Integer)
desc = db.Column(db.String)
digital = db.relationship('Pin_function', backref='analog_or_digital', lazy=True)
airFeedTypeId = db.relationship('Air_feed_type', backref='air_feed_type_id', lazy=True, uselist=False) #This allow the usage for a single element. 1-1 Rel.
#variators = db.relationship('Variator', secondary=airFeedVariator, lazy='subquery',backref=backref('airfeedvariators', lazy=True))
variators = db.relationship('airFeedVariator', lazy='dynamic')
And the forms goes as:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, IntegerField, DateTimeField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from flask_babel import lazy_gettext as _l #compile in runtime only.
#Utilizado para converter directamente os modelos em forms.
#from wtforms_alchemy import ModelForm
from app import models
from wtforms_alchemy import model_form_factory
BaseModelForm = model_form_factory(FlaskForm)
class ModelForm(BaseModelForm):
#classmethod
def get_session(self):
print('Session: ', db.session)
return db.session
class LoginForm(FlaskForm):
username = StringField(_l('Utilizador'), validators=[DataRequired()])
password = PasswordField(_l('Password'), validators=[DataRequired()])
remember_me = BooleanField(_l('Lembrar-me'))
submit = SubmitField(_l('Submeter'))
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField(
'Repetir Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField(l_('Registo'))
#Validate on submit check for this. The rise allow to write a message directly on the webpage!
def validate_username(self, username):
user = models.User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError(_l('Utilizador já existente!'))
def validate_email(self, email):
user = models.User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError(_l('Email já existente!'))
class VariatorForm(FlaskForm):
name = StringField(_l('Nome'), validators=[DataRequired()])
machineID = IntegerField(_l('Identificador'), validators=[DataRequired()])
baudrate = IntegerField(_l('Valocidade Comm'), validators=[DataRequired()])
addedAt = DateTimeField(_l('Preenchimento automatico')) # the current timestamp
def validate_machineID(self, machineID):
user = models.Variator.query.filter_by(machineID=machineID.data).first()
if user is not None:
raise ValidationError(_l('Já existente um variador registado neste endereço!'))
I do realize that I have a FlaskForm form and ModelForm on the others, I was just testing.
My problem is the following:
File "c:\app\routes.py", line 49, in SaveVariator
db.session.add(var) File "c:\gburner\lib\site-packages\sqlalchemy\orm\scoping.py", line 153, in do
return getattr(self.registry(), name)(*args, **kwargs) File "c:\gburner\lib\site-packages\sqlalchemy\orm\session.py", line 1833, in add
raise exc.UnmappedInstanceError(instance) sqlalchemy.orm.exc.UnmappedInstanceError: Class 'app.forms.VariatorForm' is not mapped
* Added Problatic route.py part *
#app.route('/savevariator', methods=['POST'])
def SaveVariator():
var = VariatorForm(request.form)
if var.validate_on_submit():
print('Variator form submit accepted!')
### I was doing this ### Trying to add the FORM to the session!
db.session.add(var)
db.session.commit()
#############################
### INSTEAD I Should be doing this: ######
variator = Variator(name=var.name, machineID=var.machineID, baudrate=var.baudrate)
db.session.add(variator)
db.session.commit()
##############################
resp = jsonify(success=True)
else:
print('Variator form submit NOT accepted!')
resp = jsonify(success=False)
return resp
I think html code would not be necessary since everything works fine until the moment where the call to save information on database happens.
Am I missing something regarding the mapping relationship in database because I have 3 tables for a many to many relationship?
Any help solve the problem would greatly appreciated.
Thank you.
* EDIT *
Thanks to Joost for point me this simple yet difficult to realize point! (I probably should pause more often).
The problem was in the route section:
#app.route('/savevariator', methods=['POST'])
def SaveVariator():
var = VariatorForm(request.form)
if var.validate_on_submit():
print('Variator form submit accepted!')
### I was doing this ### Trying to add the FORM to the session!
db.session.add(var)
db.session.commit()
#############################
### INSTEAD I Should be doing this: ######
variator = Variator(name=var.name, machineID=var.machineID, baudrate=var.baudrate)
db.session.add(variator)
db.session.commit()
##############################
resp = jsonify(success=True)
else:
print('Variator form submit NOT accepted!')
resp = jsonify(success=False)
return resp
Thanks to Joost I was able to understand where the problem was. Thanks again!
I realize it's a simple mistake, but I couldn't find much information regarding the error and solution. However if the admins decide it's not worth to keep, feel free to remove it.
Thank you.
Related
I am just beginning with python and flask and am looking to create an app. The app allows users to enter their favourite artists in one page. In the next page then allows the user to enter favourite tracks using SelectField where the artist names are given as the options.
I have the following
models:
from application import db
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, BooleanField, IntegerField, SelectField
class Artist(db.Model):
id = db.Column(db.Integer, primary_key=True)
artist_name = db.Column(db.String(45), nullable=False)
music_artist = db.relationship('Music', backref='musicbr')
def __repr__(self):
return 'Choose {}'.format(self.artist_name)
class Music(db.Model):
id = db.Column(db.Integer, primary_key=True)
track_name = db.Column(db.String(45), nullable=False)
artist_id = db.Column(db.Integer, db.ForeignKey('artist.id'), nullable=False)
class AddArtistForm(FlaskForm):
artist_name = StringField('Artist Name')
submit = SubmitField('Add Artist!')
class AddMusicForm(FlaskForm):
track_name = StringField('Track Name')
artist_name = SelectField('Artist Name', coerce=int)
submit = SubmitField('Add Track!')enter code here
and the following routes
#app.route('/')
def home():
return render_template('index.html')
#app.route('/add_artist', methods = ['GET', 'POST'])
def add_artist():
form = AddArtistForm()
if form.validate_on_submit():
new_artist = Artist(artist_name =form.artist_name.data)
db.session.add(new_artist)
db.session.commit()
return render_template('index.html', message="Artist Added!")
else:
return render_template('add_artist.html', form=form)
#app.route('/add_music', methods = ['GET', 'POST'])
def add_music():
form = AddMusicForm()
if form.validate_on_submit():
new_track = Music(track_name =form.track_name.data)
artist_choice = Artist.query.all(artist_name=form.artist_name.data)
db.session.add(new_track)
db.session.commit()
return render_template('index.html', message="Track Added!")
else:
return render_template('add_music.html', form=form)
Is someone able to help me to understand what code I need to implement here?
got this error while executing my aap.py...cna anyone help?`
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 1 - probably unsupported type.
[SQL: INSERT INTO room (r_name, created_at, created_by, r_description) VALUES (?, CURRENT_TIMESTAMP, ?, ?)]
[parameters: ('hello', <Users 13>, None)]
(Background on this error at: http://sqlalche.me/e/14/rvf5)
my module.py
from flask import Flask
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import DateTime
from sqlalchemy.sql import func
import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///C:/sqlite3/database/chat.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
phone_no = db.Column(db.Integer, unique=True, nullable=False)
city = db.Column(db.String(80), nullable=False)
rooms = db.relationship('Room', backref='users')
class Room(db.Model):
id = db.Column(db.Integer, primary_key=True)
r_name = db.Column(db.String(200), unique=False, nullable=False)
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
created_by = db.Column(db.String(200), db.ForeignKey('users.id'), nullable=False)
r_description = db.Column(db.String(200), nullable=True)
if __name__ == '__main__':
manager.run()
db.create_all()
'''
my app.py
'''
from flask import Flask, request, jsonify
from models import db, Users, Room, app
from datetime import datetime
#app.route('/user', methods=['POST'])
def add_users():
if request.method == 'POST':
name = request.json['name']
email = request.json['email']
phone_no = request.json['phone_no']
city = request.json['city']
new_user = Users(name=name, email=email, phone_no=phone_no, city=city)
db.session.add(new_user)
db.session.commit()
room = Room(r_name='hello')
room.created_by = new_user
# room.created_at = datetime(2015, 6, 5, 8, 10, 10, 10)
db.session.add(room)
db.session.commit()
return jsonify({'message': 'successfully created user'})
if __name__ == '__main__':
app.run(debug=True)
'''
room.created_by is an integer column, so you can't assign a User instance like new_user to it.
You could assign new_user.id
room.created_by = new_user.id
but it might be more elegant to make use of the rooms relationship defined on User.
This ought to work
#app.route('/user', methods=['POST'])
def add_users():
if request.method == 'POST':
name = request.json['name']
email = request.json['email']
phone_no = request.json['phone_no']
city = request.json['city']
new_user = Users(name=name, email=email, phone_no=phone_no, city=city)
room = Room(r_name='hello')
new_user.rooms.append(room)
db.session.add(new_user)
db.session.commit()
return jsonify({'message': 'successfully created user'})
Writing my first web app using flask / SQLAlchemy. I have a many to many relationship between 'persons' and 'facilities.' When I successfully add a person using the registration form, the association table does not get a row added. Do I have to insert that row manually?
Here is the pertinent part of the model:
# app/models.py
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app import db, login_manager
# [START model]
# Build secondary table for many to many between facilities and persons
workers = db.Table('workers',
db.Column('facility_id', db.Integer, db.ForeignKey('facilities.id')),
db.Column('person_id', db.Integer, db.ForeignKey('persons.id'))
)
class Facility(db.Model):
__tablename__='facilities'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
description = db.Column(db.String(128))
persons = db.relationship('Person', secondary='workers', backref='facilities', lazy = 'dynamic')
def __repr__(self):
return "<Facility name='%s')" % (self.name)
class Person(UserMixin, db.Model):
__tablename__ = 'persons'
id = db.Column(db.Integer, primary_key=True)
last_name = db.Column(db.String(60), index=True)
username = db.Column(db.String(60), index=True, unique=True)
email = db.Column(db.String(80), index=True)
password_hash = db.Column(db.String(128))
first_name = db.Column(db.String(60), index=True)
role = db.Column(db.Integer, db.ForeignKey('roles.id'))
is_person_active = db.Column(db.Boolean, index=True)
is_admin = db.Column(db.Boolean, default=False)
comments = db.Column(db.String(255))
animals = db.relationship('Animal', secondary='permissions', backref='persons', lazy = 'dynamic'))
#property
def password(self):
"""
Prevent password from being accessed
"""
raise AttributeError('password is not a readable attribute.')
#password.setter
def password(self, password):
"""
Set password to a hashed password
"""
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
"""
Check if hashed password matches actual password
"""
return check_password_hash(self.password_hash, password)
def __repr__(self):
return "<Person name='%s', '%s', '%s')" % (self.first_name, self.last_name, self.username)
# Set up user_loader
#login_manager.user_loader
def load_user(user_id):
return Person.query.get(int(user_id))
And here is the view:
# app/auth/views.py
from flask import flash, redirect, render_template, url_for
from flask_login import login_required, login_user, logout_user
from . import auth
from .forms import LoginForm, RegistrationForm
from .. import db
from ..models import Person, Facility
#auth.route('/register', methods=['GET', 'POST'])
def register():
"""
Handle requests to the /register route
Add a person to the database through the registration form
"""
form = RegistrationForm()
form.facility_id.choices = [(f.id, f.name) for f in Facility.query.order_by('name')]
if form.validate_on_submit():
person = Person(facility=form.facility_id.data,
email=form.email.data,
username=form.username.data,
first_name=form.first_name.data,
last_name=form.last_name.data,
password=form.password.data)
# add person to the database
db.session.add(person)
db.session.commit()
flash('You have successfully registered! You may now login.')
# redirect to the login page
return redirect(url_for('auth.login'))
# load registration template
return render_template('auth/register.html', form=form, title='Register')
Thanks for the support #Michael. You were close enough that I found the problem; it was that I was not adding the person to the persons collection for the facility, so no row was inserted into the workers table. I added
facility = Facility.query.filter_by(id=form.facility_id.data).first()
facility.persons.append(person)
db.session.commit()
after the existing code
db.session.add(person)
db.session.commit()
in the registration view and it is correctly inserting rows in the workers table now.
If the above https://stackoverflow.com/a/60100671/1449799 doesn't work, I wonder if it's as simple as a spelling issue? You've said that the back ref from Facility to Person should be called facilities. perhaps in your call to the Person constructor in your register function you should change:
person = Person(facility=form.facility_id.data,
to
person = Person(facilities=[form.facility_id.data],
Perhaps this question is a duplicate of https://stackoverflow.com/a/25669256/1449799 ? It seems that the issue you're having is that in your register() function, there's no mention of facilities.
Without changing your model classes (e.g. to have the Person model know about its connected facilities in addition to the reverse of what you do have now in Facility for Person), I think you may be able to do something in register() like:
#this should maybe come after db.session.add(person), but before db.session.commit()
selected_facility = Facility.query.get(form.facility_id.data)
selected_facility.persons.append(person)
or alternatively
#this should maybe come after db.session.add(person), but before db.session.commit()
selected_facility = Facility.query.get(form.facility_id.data)
person.facilities.append(selected_facility)
I am attempting to create Admin-user changeable drop down boxes that will then be used in another model to track transactions. The db.relationship approach doesn't work very well as the validation list will be changed almost daily. Think of a delivery tracking app (class DeliveryLog is where I store every delivery, class Driver is where I store names of drivers, class Product is where I store what they delivered).
imports and Models:
import os
from flask import Flask, url_for, redirect, render_template, request, abort
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required, current_user
import flask_admin
from flask_admin.contrib import sqla
import sys
# Create Flask application
app = Flask(__name__)
app.config.from_pyfile('config.py')
db = SQLAlchemy(app)
# Define models
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return self.name
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return self.email
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
class Driver(db.Model):
id = db.Column(db.Integer, primary_key=True)
driver = db.Column(db.String(255), unique=True)
def __str__(self):
return self.driver
def __repr__(self):
return self.driver
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
product = db.Column(db.String(255), unique=True)
def __str__(self):
return self.product
def __repr__(self):
return self.product
class DeliveryLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DATE,index=True)
operator = db.Column(db.String(127))
on_premises = db.Column(db.Boolean())
cargo = db.Column(db.String(127))
def __str__(self):
return self.operator + ' on ' + str(self.date)
Views, security context and main
class MyAdminView(sqla.ModelView):
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.has_role('superuser'):
return True
return False
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
# login
return redirect(url_for('security.login', next=request.url))
class MyLogView(MyAdminView):
form_extra_fields = {
'cargo': sqla.fields.QuerySelectField(label='Custom Cargo',
query_factory=db.session.query(Product).all)
}
#Product.query.all
#db.session.query(Product).all
#app.route('/')
#login_required
def index():
return render_template('index.html')
# Create admin
admin = flask_admin.Admin(
app,
'Fruit Tracker',
base_template='my_master.html',
template_mode='bootstrap3',
)
# Add model views
admin.add_view(MyAdminView(Role, db.session))
admin.add_view(MyAdminView(User, db.session))
admin.add_view(MyAdminView(Driver, db.session))
admin.add_view(MyAdminView(Product, db.session))
admin.add_view(MyLogView(DeliveryLog, db.session))
# define a context processor for merging flask-admin's template context into the
#security.context_processor
def security_context_processor():
return dict(
admin_base_template=admin.base_template,
admin_view=admin.index_view,
h=flask_admin.helpers,
get_url=url_for
)
if __name__ == '__main__':
db.create_all()
if 'debug' in sys.argv:
app.run(host='0.0.0.0', port=5001, debug=True)
else:
app.run(host='0.0.0.0', port=80)
While the form generates fine and has the latest values are in the drop down, the representation of the fields in the model are incorrect to SQL Alchemy and I get :
sqlalchemy.exc.InterfaceError
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding
parameter 3 - probably unsupported type. [SQL: 'INSERT INTO
delivery_log (date, operator, on_premises, cargo) VALUES (?, ?, ?,
?)'] [parameters: ('2016-08-18', 'Frank', 1, kiwi)]
Please keep in mind that this is a simplified example that may teach others how to sub class views in Flask-Admin, the real application will have large forms (20 fields + at least five drop down boxes), so single-field to single-class overrides don't work here. I think that this pattern of allowing admin-users change how the application behaves is great way to offload maintenance of the application onto non-developers. Thank you
It looks like SQLlite doesn't like your on_premises data type. Try converting the '1' to a real Python boolean value, True. Or double check that the '1' is truly an int, and not a string.
I use django-rest-auth and django-allauth about user registration/login using user's Facebook profile.
Now, I can take some standard information from Facebook (default for all auth).
Is possible to retrieve also list of avatar-pictures?
How can make it in allAuth?
If you want to get avatar pictures in a django template:
<img src="{{ user.socialaccount_set.all.0.get_avatar_url }}" />
Is that what you were trying to do?
Let me quickly described how i did it (using all_auth and rest_auth).
Basically, 'allauth => socialaccount' provide user_signed_up
and user_logged_in signals. So you need to catch them and get
sociallogin object's extra data and populate your avatar_url.
Step-1: Create a UserProfile model: (to store avatar_url)
#models.py
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, verbose_name='user', related_name='profile')
avatar_url = models.CharField(max_length=256, blank=True, null=True)
def __str__(self):
return force_text(self.user.email)
class Meta():
db_table = 'user_profile'
Step-2: Catch 'user_signed_up' signal and populate `avatar_url'
# signals.py
from allauth.account.signals import user_signed_up, user_logged_in
#receiver(user_signed_up)
def social_login_fname_lname_profilepic(sociallogin, user):
preferred_avatar_size_pixels=256
picture_url = "http://www.gravatar.com/avatar/{0}?s={1}".format(
hashlib.md5(user.email.encode('UTF-8')).hexdigest(),
preferred_avatar_size_pixels
)
if sociallogin:
# Extract first / last names from social nets and store on User record
if sociallogin.account.provider == 'twitter':
name = sociallogin.account.extra_data['name']
user.first_name = name.split()[0]
user.last_name = name.split()[1]
if sociallogin.account.provider == 'facebook':
f_name = sociallogin.account.extra_data['first_name']
l_name = sociallogin.account.extra_data['last_name']
if f_name:
user.first_name = f_name
if l_name:
user.last_name = l_name
#verified = sociallogin.account.extra_data['verified']
picture_url = "http://graph.facebook.com/{0}/picture?width={1}&height={1}".format(
sociallogin.account.uid, preferred_avatar_size_pixels)
if sociallogin.account.provider == 'google':
f_name = sociallogin.account.extra_data['given_name']
l_name = sociallogin.account.extra_data['family_name']
if f_name:
user.first_name = f_name
if l_name:
user.last_name = l_name
#verified = sociallogin.account.extra_data['verified_email']
picture_url = sociallogin.account.extra_data['picture']
user.save()
profile = UserProfile(user=user, avatar_url=picture_url)
profile.save()
Hope this will help you.
In case you don't want to use Django Signals, you can extend DefaultSocialAccountAdapter from allauth.socialaccount.adapter and override populate_user:
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.account.utils import user_field
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data)
try:
picture = sociallogin.account.extra_data['picture']
user_field(user, "profile_photo", picture)
except (KeyError, AttributeError):
pass
return user
Note you should also change DefaultSocialAccountAdapter in settings.py:
SOCIALACCOUNT_ADAPTER = 'some_app.path.to.CustomSocialAccountAdapter'