Creating parent and child at the same time. Flask SqlAlchemy - python

I have created the model below to store user and profile data separately in my database
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(255))
profile = db.relationship('Profile', backref='Profile', uselist=False)
class Profile(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
last_name = db.Column(db.String(25))
email = db.Column(db.String(25), unique=True)
phone_number = db.Column(db.String(25), unique=True)
post_code = db.Column(db.String(25))
house_number = db.Column(db.String(25))
user = db.Column(db.Integer, db.ForeignKey('user.id'))
I have attempted to populate Profile Model via this method, however, it does not work.
#routes.route('/register', methods = ['POST'])
def register():
if request.method == 'POST':
data = request.get_json()
new_user = User(username = data['username'], password = data['password'])
new_user.profile.first_name = data['first_name']
new_user.profile.last_name = data['last_name']
new_user.profile.email = data['email']
new_user.profile.phone_number = data['phone_number']
new_user.profile.post_code = data['post_code']
new_user.profile.house_number = data['house_number']
db.session.add(new_user)
db.session.commit()
return {'msg' : 'sucess'}
I get this error, would you please explain what I am doing wrong? I noticed that User.profile column is not present inside my database, however, I thought that was normal for a ForiegnKey?
File "C:\routes\register.py", line 11, in register
new_user.profile.first_name = data['first_name']
AttributeError: 'NoneType' object has no attribute 'first_name'
What is the solution ? I am assuming I need to create Profile() model separately but how do I simultaneously link that to an uncommitted User() model?

A few ways to do this, but you probably want to create an __init__(...) for user where you initialize a Profile object.
Something like this...
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(255))
profile = db.relationship('Profile', backref='Profile', uselist=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.profile = Profile()

Related

Undefined attribute error for accessing relation

I have defined two model with relation between them as below:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), nullable=False, unique=False)
db.relationship('User', backref='role', lazy='dynamic')
def __init__(self, name):
self.name = name
def __repr__(self):
return f'<Role id={self.id} name={self.name}>'
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(40), unique=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __init__(self, username, password, role_id):
self.username = username
self.password = password
self.role_id = role_id
def __repr__(self):
return f'<User id={self.id} username={self.username} password={self.password}>'
Then inside shell I created entries as below:
> admin_role = Role('Admin')
> db.session.add(admin_role)
> db.session.commit()
> admin_user = User('adminusername', 'adminpassword',admin_role.id)
> db.session.add(admin_user)
> db.session.commit()
When I try to query model I get perfect result:
>>> admin_role = Role.query.first()
>>> admin_user = User.query.first()
>>> print(admin_role)
<Role id=1 name=Admin>
>>> print(admin_user)
<User id=1 username=adminusername password=adminpassword>
But when I try to access relation
print(admin_role.users)
print(admin_user.role)
I get errors Role object has no attribute users and User object has no attribute role respectively.
Typo? You have to assign db.relationship() instance to a variable.
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), nullable=False, unique=False)
- db.relationship('User', backref='role', lazy='dynamic')
+ users = db.relationship('User', backref='role', lazy='dynamic')

Access multiple database in SQLAlchemy

This is my code:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////root/Desktop/Social_Network/users.db'
app.config['SQLALCHEMY_BINDS'] = {'posts': 'sqlite:////root/Desktop/Social_Network/posts.db'}
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(40), unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
joined_at = db.Column(db.DateTime(),default =datetime.datetime.now)
is_hero = db.Column(db.Boolean(),default=False)
class Post(db.Model):
__bind_key__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
user = db.Column(db.String(50))
post = db.Column(db.String(255))
score = db.Column(db.Integer, nullable=False)
downVotes = db.Column(db.Integer, nullable=False )
posted_time = db.Column(db.DateTime(),default =datetime.datetime.now)
upVotes = db.Column(db.Integer,nullable=False)
dict_ = {}
def get_all_users():
users_ = User.query.all()
global dict_
for user in users_:
dict_[user.email] = user.password
return dict_
I have connected multiple databases in SQLAlchemy. My problem is whenever I run :
users_ = User.query.all()
. It returns the User object, but after I run the change it to post, something like this:
post = Post.query.all()
It returns none. Any help regarding this issue?

How to set form element name?

I have a web application written in Flask that uses WTForms, SQLAlchemy and corresponding Flask extensions, Flask-SQLAlchemy, Flask-WTF and WTForms-Alchemy.
For the following model:
class User(db.Model, UserMixin):
"""
Represents a registered user.
#TODO Override setter for password so it is always encrypted.
#TODO Add last_login column
"""
__tablename__ = 'User'
def __init__(self, username=None, password=None, email=None):
self.username = username
self.email = email
self.password = encrypt(password)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, default=u'Anonymous Lifter')
username = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
I try to create a Form the following way:
class LoginForm(ModelForm):
""" Form used to login a user that does not use social login. """
class Meta:
model = User
only = [u'username', u'password']
field_args = {u'password': {'widget': PasswordInput()}}
remember_me = BooleanField(u'Remember me')
My problem is that when I display the form on my page both username and password appear lower-cased. How can I set their labels(?)/names(?) to upper-case?
Use the info property when defining your columns in your model. For example:
class User(db.Model, UserMixin):
"""
Represents a registered user.
#TODO Override setter for password so it is always encrypted.
#TODO Add last_login column
"""
__tablename__ = 'User'
def __init__(self, username=None, password=None, email=None):
self.username = username
self.email = email
self.password = encrypt(password)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, default=u'Anonymous Lifter', info={'label': 'Name'})
username = db.Column(db.String, nullable=False, info={'label': 'Username'})
password = db.Column(db.String, nullable=False, info={'label': 'Password'})
Or use the field_args when defining your form:
class LoginForm(ModelForm):
""" Form used to login a user that does not use social login. """
class Meta:
model = User
only = [u'username', u'password']
field_args = {
u'password': {
'widget': PasswordInput(),
'label': 'PASSWORD'
},
u'username': {'label': 'USERNAME'},
}
remember_me = BooleanField(u'Remember me')

Populate WTForm MultiCheckboxField with Flask-Principal Roles

I'm working on an edit user page for my flask app, and I can't seem to figure out how to render the user's current roles like I do other variables, such as email.
Here's my models, form, and view:
#Flask-Principal Role Model
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 __repr__(self):
return '<Role %r>' % (self.name)
#SQLALchemy User Model
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))
first_name = db.Column(db.String(128))
last_name = db.Column(db.String(128))
business_name = db.Column(db.String(128))
active = db.Column(db.Boolean())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
#WTForms User Form
class UserForm(Form):
first_name = StringField('first name', validators= [Required()])
last_name = StringField('last name', validators= [Required()])
business_name = StringField('business name', validators= [Required()])
email = StringField('email', validators = [Required(), Email()])
active = BooleanField('active')
roles = MultiCheckboxField('roles', coerce=int)
#Edit User View
#app.route('/admin/users/<id>/edit/', methods = ['GET', 'POST'])
#roles_required('admin')
def edit_user(id):
user = User.query.filter_by(id = id).first()
editform = UserForm()
# This is how I've assigned choices for other MultiCheckboxField forms, but I haven't
# needed to populate the MultiCheckboxField from a user model before with role objects.
editform.roles.choices = [(x.id,x.name) for x in Role.query.all()]
if editform.validate_on_submit():
pass
editform.first_name.data = user.first_name
editform.last_name.data = user.last_name
editform.business_name.data = user.business_name
editform.email.data = user.email
editform.active.data = user.active
#The below doesn't show all the available roles, just the current roles assigned.
editform.roles.data = user.roles
return render_template("user_edit.html",
title = "Edit User",
user = user,
editform = editform)
So then, does anyone know how to have WTForms display all the Roles available, and have checked the ones which are currently in the user.roles list?
data for a multi-select field is a list of form_data after it has been coerced by your coerce callable - so what you provide needs to match what's available as the first entry in each tuple you provide to choices - in this case, an integer. That said, if you change your code to:
editform.roles.data = [role.id for role in user.roles]
you should see all the appropriate checkboxes checked.

SQLAlchemy PostgreSQL Pyramid update issue

I have the following model:
class Employee (Base):
__tablename__ = 'employees'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(300), unique=True, nullable=False)
phone_a = Column(String(20), nullable=False)
phone_b = Column(String(20))
email_a = Column(String(400), nullable=False)
email_b = Column(String(400))
address = Column(String)
charge = Column(String(100), nullable=False)
active = Column(Boolean, default=True)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
modified = Column(DateTime, onupdate=datetime.datetime.now)
def __init__(self):
self.active = True
self.created = datetime.datetime.now()
def __unicode__(self):
return self.name
I wrote the add view for it, very basic:
employee = Employee()
form = Form(request, EmployeeSchema(), obj = employee)
if form.validate():
employee = form.bind(Employee())
try:
DBSession.add(employee)
DBSession.flush()
return HTTPFound(location = request.route_url('employees'))
except IntegrityError:
message = 'Oops!'
And it works well. But the UPDATE view doesn't. I just does not save. According to the tutorial basically with the same code it should work. But it doesn't, SQLAlchemy tries to insert a new object instead of just updating it. I tried
import transaction
transaction.commit()
But no success.
_id = request.matchdict['employeeid']
employee = DBSession.query(Employee).filter_by(id=_id).first()
form = Form(request, EmployeeSchema(), obj = employee)
if form.validate():
employee = form.bind(Employee())
try:
DBSession.add(employee)
return HTTPFound(location = request.route_url('employees'))
except IntegrityError:
message = ''
You need to bind to the item, you do not need to add a new Employee() instance:
_id = request.matchdict['employeeid']
employee = DBSession.query(Employee).get(_id)
form = Form(request, EmployeeSchema(), obj=employee)
if form.validate():
form.bind(employee)
return HTTPFound(location = request.route_url('employees'))
That's it.

Categories

Resources