Using
Flask
Flask-sqlalchemy
Sqlalchemy
Jquery
Datatables (jquery plugin)
Jeditable (jquery plugin)
Consider this user class ( straight out of flask-sqlalchemy docs):
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
The datatables makes an ajax request and populates the table. Each td then is made editable in place with jeditable. As soon as a td is modified, jeditable makes a POST request to localhost/modify containing:
The row id(the same id from the user class)
The new modified value
The column of the table that was altered ( for the sake of argument let's assume that there are three columns id/username/email) (int)
Now, i'd like that in the function that handles localhost/modify i take the row id, make a user object and query the db for that specific row, see what property needs to be modified, modify it with the new value and commit to the db. Here's what i got so far:
#app.route('/modify', methods=['GET', 'POST'])
def modify()
if request.method == 'POST' :
user = user.query.get(form.row_id)
if form.column_id == int(0):
user.id = form.value
elif form.column_id == int(1):
user.username = form.value
elif form.column_id == int(2):
user.email = form.value
else:
pass
db.session.commit()
return 'ok'
This way, yes it does work but theremust be amore beautiful approach. This one doesn't seem very...pythonic
Mihai
Use a map of column ID to attribute name.
colmap = {
0: 'id',
1: 'username',
2: 'email',
}
#app.route('/modify', methods=['GET', 'POST'])
def modify()
if request.method == 'POST' :
user = user.query.get(form.row_id)
try:
setattr(user, colmap[form.column_id], form.value)
except KeyError:
pass
db.session.commit()
return 'ok'
Related
I've created a form which takes user's name and their email address. I get this data from the form and put it into a sqlite3 database in the following way:
#app.route('/my_form', methods=["GET", "POST"])
def form_data():
if request.method == "POST":
user_name = request.form["name"]
new_user = form_database(name=user_name)
user_email = request.form["email"]
new_user_email = form_database(email=user_email)
try:
db.session.add(new_user)
db.session.add(new_user_email)
db.session.commit()
return redirect("/my_form")
Current result: each data entry gets recorded into a new row:
1|Jack||||||||||
2||svisto#hotmail.com|||||||||
Desirable result: each data entry gets recorded into the same row:
1|Jack|svisto#hotmail.com|||||||||
Question: How can I change the code such that I get the desirable result?
Lets say you have a User class in your model like this:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer,
primary_key=True)
username = db.Column(db.String(32),
index=False,
unique=True,
nullable=False)
email = db.Column(db.String(64),
index=True,
unique=True,
nullable=False)
Then you can do this in your try block
try:
new_user = User(user_name=user_name,
email=email)
db.session.add(new_user)
db.session.commit()
Solution I found:
I fused:
new_user = form_database(name=user_name) and new_user_email = form_database(email=user_email)
together such that the code looks like this:
#app.route('/my_form', methods=["GET", "POST"])
def form_data():
if request.method == "POST":
user_name = request.form["name"]
user_email = request.form["email"]
new_user_details = form_database(name=user_name, email=user_email)#assigns 2 form inputs for both columns in the database model to the same variable
try:
db.session.add(new_user_details)#adds that variable to the database as one entry, hence in one row but different columns
db.session.commit()
I have two tables, named projects and actions and every project contain several action
class Projet(db.Model):
__tablename__ = 'projets'
id = db.Column(db.Integer, primary_key=True)
nom_projet = db.Column(db.String(100))
description_projet = db.Column(db.String(800))
date_affectation = db.Column(db.DateTime, nullable = False)
statut_projet = db.Column(db.String(100))
admin_id = db.Column(db.Integer, db.ForeignKey('admins.id'))
actions = db.relationship('Action', backref='projet',
lazy='dynamic')
def __repr__(self):
return '<Projet: {}>'.format(self.id)
class Action(db.Model):
__tablename__ = 'actions'
id = db.Column(db.Integer, primary_key=True)
projet_id = db.Column(db.Integer, db.ForeignKey('projets.id'))
description = db.Column(db.String(1000))
statut_action = db.Column(db.String(100))
date_action = db.Column(db.DateTime, nullable = False)
date_execution = db.Column(db.DateTime, nullable = True)
def __repr__(self):
return '<Action: {}>'.format(self.id)
my problem is, I need to create a new action based on an existing project as shown in image,
I need to click on add button and he must redirect me to action form with the name of project auto-selected, and I entre the action details.
this is my first code to add action:
#auth.route('/action/add', methods=['GET', 'POST'])
#login_required
def add_action():
form = ActionForm()
if form.validate_on_submit():
action = Action(
projet = form.projet.data,
description = form.description.data,
statut_action = form.statut_action.data,
date_action = form.date_action.data,
date_execution = form.date_execution.data
)
try:
db.session.add(action)
db.session.commit()
flash('You have successfully added a new action.')
except:
flash('Error: action name already exists.')
return redirect(url_for('auth.list_projets'))
return render_template('admin/actions/action.html', action="Add", form=form,
title="ADD ACTION")
Steps:
Update the URL to include project_id as path param: ex: /project/1/actions/add is meant to load a page with new action form for project with id 1
Update the links to add new action in the previous page(as shown in the screenshot) as per step 1
Remove project field from ActionForm as it is handled using path param
Update "new action form" page to show product name coming in product_name variable
Try,
#auth.route('/project/<project_id>/action/add', methods=['GET', 'POST'])
#login_required
def add_action(project_id):
form = ActionForm()
project = Project.query.get(project_id)
if not project:
flash('Error: invalid project')
abort(404)
if form.validate_on_submit():
action = Action(
project = project,
description = form.description.data,
statut_action = form.statut_action.data,
date_action = form.date_action.data,
date_execution = form.date_execution.data
)
try:
db.session.add(action)
db.session.commit()
flash('You have successfully added a new action.')
except:
flash('Error: action name already exists.')
return redirect(url_for('auth.list_projets'))
return render_template('admin/actions/action.html', action="Add", form=form,
title="ADD ACTION", project_name=project.name)
UPDATE / CLARIFICATION
I confirmed that this strange behavior only occurs on the macOS machine, moving everything to a windows machine (using sqlite and doing a fresh init and migrate) doesn't cause the error... doing the same on my High Sierra box does cause the odd error.
Is anyone familiar with some known difference between sqlalchemy on Windows and macOS that might help?
Short version... I'm getting an integrity error (unique constraint) after I try to commit ANY entry to the DB, even if there are NO EXISTING entries at all in the table... why?
DETAILS
I've built a FLASK project (roughly based on the Miguel Grinberg Flask Maga Tutorial) using postgresql and sqlalchemy, the front-end has a page to register a user with a confirmation email (which works fine)... to save time I've written a route (see below) which pre-loads a confirmed user to the Users database, this user is the ONLY user in the Users table and I only visit the route ONE TIME.
After a successful commit I get an IntegrityError "duplicate key value violates unique constraint". This route only adds ONE user to an existing EMPTY Users table. The data IS successfully saved to the DB, the user can log in, but an error gets thrown. I get a similar error (see below) but am focusing on this route as an example because it is shorter than other views I've written.
EXAMPLE OF ROUTE CAUSING UNIQUE CONSTRAINT ERROR
#main.route('/popme')
##login_required
def popme():
## add user
u1 = User()
u1.email = 'user#domain.com'
u1.username = 'someuser'
u1.password_hash = 'REMOVED'
u1.confirmed = '1'
u1.role_id = 3
u1.name = 'Some User'
db.session.add(u1)
db.session.commit()
flash('User someuser can now login!')
return redirect(url_for('main.index'))
I only started getting this error after moving the entire project from a Windows machine to a MacOS machine. I'm running Python 3.6 in a virtual environment, this error occurs if I'm using sqlite3 or postgresql.
I've written a much longer route which pre-fills in about 20 other tables successfully (does on commit() at the end, all data IS stored in the DB), however I get an IntegrityError "duplicate key value violates unique constraint" every time for a seemingly random entry. I've destroyed the DB, done an init, migrated... each time when the commit() is called a IntegrityError is thrown, each time on a different table, there is no apparent reasoning.
BELOW IS USER MODEL
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
confirmed = db.Column(db.Boolean, default=False)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
name = db.Column(db.String(64))
last_seen = db.Column(db.DateTime(), default=datetime.utcnow)
def ping(self):
self.last_seen = datetime.utcnow()
db.session.add(self)
#property
def password(self):
raise AttributeError('password is not a readable attribute')
#password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
def generate_confirmation_token(self, expiration=3600):
s = Serializer(current_app.config['SECRET_KEY'], expiration)
return s.dumps({'confirm': self.id})
def confirm(self, token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except:
return False
if data.get('confirm') != self.id:
return False
self.confirmed = True
db.session.add(self)
return True
def generate_reset_token(self, expiration=3600):
s = Serializer(current_app.config['SECRET_KEY'], expiration)
return s.dumps({'reset': self.id})
def reset_password(self, token, new_password):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except:
return False
if data.get('reset') != self.id:
return False
self.password = new_password
db.session.add(self)
return True
def generate_email_change_token(self, new_email, expiration=3600):
s = Serializer(current_app.config['SECRET_KEY'], expiration)
return s.dumps({'change_email': self.id, 'new_email': new_email})
def change_email(self, token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except:
return False
if data.get('change_email') != self.id:
return False
new_email = data.get('new_email')
if new_email is None:
return False
if self.query.filter_by(email=new_email).first() is not None:
return False
self.email = new_email
db.session.add(self)
return True
def can(self, permissions):
return self.role is not None and (self.role.permissions & permissions) == permissions
def is_administrator(self):
return self.can(Permission.ADMINISTRATOR)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
if self.email == current_app.config['FLASKY_ADMIN']:
self.role = Role.query.filter_by(permissions=0xff).first()
if self.role is None:
self.role = Role.query.filter_by(default=True).first()
def __repr__(self):
return '<User %r>' % self.username
I've tried Sql-alchemy Integrity error but its my understanding that sqlalchemy does auto-increment primary keys.
UPDATED INTEGRITY ERROR
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "ix_users_email"
DETAIL: Key (email)=(worldbmd#gmail.com) already exists.
[SQL: 'INSERT INTO users (email, username, password_hash, confirmed, role_id, name, last_seen) VALUES (%(email)s, %(username)s, %(password_hash)s, %(confirmed)s, %(role_id)s, %(name)s, %(last_seen)s) RETURNING users.id'] [parameters: {'email': 'user#domain.com', 'username': 'someuser', 'password_hash': 'REMOVED', 'confirmed': '1', 'role_id': 1, 'name': 'Some User', 'last_seen': datetime.datetime(2018, 7, 16, 17, 27, 13, 451593)}]
I also got into the same problem..as Joost said flask app runs twice. So we need to set it to run only once. We can achieve it by adding use_reloader=False like:
if __name__ = "__main__":
app.run(debug=True, use_reloader=False)
or
we can directly set debug=False,
if __name__ = "__main__":
app.run(debug=False)
The integrity error is caused by trying to add a blister to the blisters table with a non unique property. I think your model looks something like this:
class Blister(db.Model):
__tablename__ = 'blisters'
id = db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(64), unique=True)
notes= db.Column(db.String(64))
cost= db.Column(db.Float)
And you're trying to add a blister with a name Small round dome which is already in the blisters table in the database, therefore causing the Integrity Error.
I want to preload form data in wtforms with data from my database without knowing the column names.
this works so long as I know the column name I want.
form.column.data=User.query.get(1).first().column
what I want to do is go over all the columns like so:
for attr, value in User.query.get(1).__dict__.iteritems():
form.attr.data = value
doing so gives me the error:
AttributeError: 'EditUsersForm' object has no attribute 'attr'
Here is a snippet of the code I'm trying to get to work specifically.
forms.py
class EditUsersForm(ModelForm):
class Meta:
model=User
exclude=['password_hash']
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(32), index=True, unique=True)
password_hash = db.Column(db.String(128))
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
#mobilitys = db.relationship('Mobility', backref='username', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
def __repr__(self): # pragma: no cover
return '<user> %r' % (self.username)
views.py
#app.route('/manpower_edit_users', methods=['GET', 'POST'])
#login_required
def manpower_edit_users():
form=QueryAllUsers(request.form)
forms=EditUsersForm(request.form)
'''
if forms.validate():
print("validate")
print("forms.validate=",forms.validate)
flash("User added")
'''
if request.method == "POST":
for attr, value in User.query.get(user_list).__dict__.iteritems():
forms.attr.data=value
elif request.method == "GET":
for attr, value in User.query.get(1).__dict__.iteritems():
forms.attr.data=value
return render_template('manpower_edit_users.html',title='Manpower Edit User', form=form, forms=forms)
edit_user_form = EditUsersForm(obj = User.query.get(1))
You can populate a form from data. The keyword argument obj in documentation of WTForms said:
obj – If formdata has no data for a field, the form will try to get it from the passed object.
In my application i have a QuerySelectField to populate a dropdown menu.
I get the choices details for the queryselectfield from the db. Once user select any choice from dropdown menu and click on Submit button which is a POST method, i want to pass the value that user selected from the dropdown to a db to store. But it always return the value None from the queryselectfield. So db stores the data as None.
models.py
class Orders(db.Model):
__tablename__ = 'orders'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
email = db.Column(db.String(120))
resturant = db.Column(db.String(64))
food = db.Column(db.String(64)))
forms.py
def possible_res():
return Resturant.query.all()
def possible_menu():
return Menu.query.all()
class OrderForm(Form):
sel_res = QuerySelectField(query_factory=possible_res,
get_label='name')
sel_menu = QuerySelectField(query_factory=possible_menu,
get_label='food',
allow_blank=False
)
submit = SubmitField("Confirm")
views.py
#app.route('/resturant', methods=['GET','POST'])
def resturant():
form = OrderForm()
if request.method == 'GET':
test = form.sel_menu.data
return render_template("make_order.html", form=form, test=test)
else:
a = User.query.filter_by(email = session['email']).all()
for u in a:
name = u.firstname
b = Orders(name=name, email=session['email'])
b.resturant = form.sel_res.data
b.food = form.sel_menu.data
db.session.add(b)
db.session.commit()
return redirect('/')
QuerySelectField#__init__'s query_factory argument should return a SQLAlchemy Query object, not a Python list. WTForms will materialize the query itself, by iterating over it. Simply change your factory functions to return the query attribute, rather than the list:
def possible_res():
return Resturant.query
def possible_menu():
return Menu.query