SQLAlchemy PostgreSQL Pyramid update issue - python

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.

Related

Do I need to explicitly pass an object to my relationship. (SQL Alchemy AttributeError: 'str' object has no attribute '_sa_instaince_state')

I've been working on a web app for work and I've run into an issue creating objects of my db.Model. I have a ShiftStamp form which lets people enter the shift they worked, however when they submit it I get this error
(SQL Alchemy AttributeError: 'str' object has no attribute '_sa_instaince_state')
Original Code:
Users()
class Users(People, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, ForeignKey('people.id'), primary_key=True)
#password = db.Column(db.String(150), nullable=False)
password_hash = db.Column(db.String(150), nullable=False)
system_level_id = db.Column(db.String(50), ForeignKey('systemlevels.level'), nullable=False, default='GROUND')
system_level = db.relationship('SystemLevels', back_populates='users')
#make sure to uncoment Campaigns along with this
#candidacies = db.relationship('Campaigns', back_populates="candidate")
admin_campaigns = db.relationship('Campaigns', secondary=admins, back_populates="admins")
shiftstamps = db.relationship('ShiftStamps', back_populates="user")
ShiftStamps()
class ShiftStamps(db.Model):
__tablename__ = 'shiftstamps'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, ForeignKey('users.id'))
user = db.relationship('Users', back_populates='shiftstamps')
start_time = db.Column(db.DateTime, nullable=False)
end_time = db.Column(db.DateTime, nullable=False)
minutes= db.Column(db.Integer, nullable=False)
activity_id = db.Column(db.String(50), ForeignKey('activities.activity'))
activity = db.relationship("Activities", back_populates='shiftstamps')
date_added = db.Column(db.DateTime, default=datetime.utcnow())
class Activities(db.Model):
__tablename__ = 'activities'
activity = db.Column(db.String(50), nullable=False, primary_key=True)
shiftstamps = db.relationship("ShiftStamps", back_populates="activity")
This is prepopulated with activity as the primary_key
| activity |
| -------- |
| admin|
|calling|
|canvass|
|litdrop|
In my route I query for each value as a list since [values] can be passed into SelectField instead of [(value,label)]
route
#shift_route.route('/shift_add', methods=['GET', 'POST'])
def shift_add():
form = ShiftStampForm()
choiceMath = [(str(u.id), str(u.first_name + ' ' + u.last_name)) for u in Users.query.order_by('first_name')]
form.user.choices = choiceMath
form.activity.choices = [str(a.activity) for a in Activities.query.order_by()]
if form.validate_on_submit():
founduser = Users.query.filter_by(id=form.user.data).first()
shiftstamp = ShiftStamps(user_id=founduser.id, start_time=datetime.combine(form.date.data, datetime.strptime(form.start_time.data, '%H:%M:%S').time()),
end_time=datetime.combine(form.date.data, datetime.strptime(form.end_time.data, '%H:%M:%S').time()),
activity=form.activity.data
)
shiftstamp.minutes = shiftstamp.end_time - shiftstamp.start_time.total_seconds() / 60
comparedShift = ShiftStamps.query.filter_by(user_id=shiftstamp.user_id, start_time=shiftstamp.start_time).first()
if comparedShift:
flash("This Shift Already Exists.", category='error')
else:
db.session.add(shiftstamp)
db.session.commit()
form.user.data = ''
form.date.data = ''
form.start_time.data = ''
form.end_time.data = ''
form.activity.data = ''
flash("Shift Added Successfully!", category='success')
return redirect(url_for('views.home'))
return render_template('/shift/shift_add.html', form=form)
I modeled my classes after the described Many to One relationship pattern in the SQLAlchemy Docs. The solutions here and here explain how I need to pass an instance of my user to the ShiftStamp() I'm creating in my route, however this requires querying for that user. This is the code where I do that
#shift_route.route('/shift_add', methods=['GET', 'POST'])
def shift_add():
form = ShiftStampForm()
choiceMath = [(str(u.id), str(u.first_name + ' ' + u.last_name)) for u in Users.query.order_by('first_name')]
form.user.choices = choiceMath
form.activity.choices = [str(a.activity) for a in Activities.query.order_by()]
if form.validate_on_submit():
founduser = Users.query.filter_by(id=form.user.data).first()
shiftstamp = ShiftStamps(user_id=founduser.id, user=founduser, start_time=datetime.combine(form.date.data, datetime.strptime(form.start_time.data, '%H:%M:%S').time()),
end_time=datetime.combine(form.date.data, datetime.strptime(form.end_time.data, '%H:%M:%S').time()),
activity=form.activity.data
)
shiftstamp.minutes = shiftstamp.end_time - shiftstamp.start_time.total_seconds() / 60
comparedShift = ShiftStamps.query.filter_by(user_id=shiftstamp.user_id, start_time=shiftstamp.start_time).first()
if comparedShift:
flash("This Shift Already Exists.", category='error')
else:
db.session.add(shiftstamp)
db.session.commit()
form.user.data = ''
form.date.data = ''
form.start_time.data = ''
form.end_time.data = ''
form.activity.data = ''
flash("Shift Added Successfully!", category='success')
return redirect(url_for('views.home'))
return render_template('/shift/shift_add.html', form=form)
And yet it still doesn't work for some reason and I get the same error
I've also thought that one solution that has worked for the production of my app is just leave out the relationship keyword and work with ForeignKeys but that seems like going around a problem instead of using this as an opportunity to learn and find the solution that will help me for the future

Creating parent and child at the same time. Flask SqlAlchemy

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()

Python AttributeError: 'str' object has no attribute '_sa_instance_state'

So, I am trying to add the images name that I save in the specified directory, and its getting added to the database However this error keeps coming up. Although the images keep getting saved in the specified directory. Here are all my files Models.py
class Tickets(db.Model):
id = db.Column(db.Integer,primary_key=True)
title = db.Column(db.String(100),nullable=False)
ticket_text = db.Column(db.Text,nullable=False)
date_posted = db.Column(db.DateTime,nullable=False,default=datetime.utcnow)
status = db.Column(db.String(30), nullable=False)
priority = db.Column(db.String(30), nullable=False)
created_by_id = db.Column(db.Integer, nullable=False)
expert_id = db.Column(db.Integer, db.ForeignKey('users.id'),nullable=False)
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'),nullable=False)
comment = db.relationship('Comment', backref='title', lazy='dynamic')
attach = db.relationship('Attachment', backref='ticket', lazy='dynamic')
class Attachment(db.Model):
id = db.Column(db.Integer, primary_key=True)
file = db.Column(db.String(140))
ticket_id = db.Column(db.Integer, db.ForeignKey('tickets.id'), nullable=False)
routes.py
#app.route('/ticket/<ticket_id>',methods=['GET','POST'])
#login_required
def ticket(ticket_id):
ticket = Tickets.query.get_or_404(ticket_id)
com = Comment.query.filter_by(ticket_id=ticket.id).first()
form = CommentForm()
attachform = AttachForm()
if form.validate_on_submit() and form.body.data:
comment = Comment(body=form.body.data,ticket_id=ticket_id,author = current_user.username)
db.session.add(comment)
db.session.commit()
flash('Your comment has been published.')
return redirect(url_for('ticket', ticket_id=ticket_id))
if attachform.validate_on_submit():
if attachform.file.data:
picture_file = save_file(attachform.file.data)
attachment = Attachment(file=picture_file,ticket_id=ticket_id)
db.session.add(attachment)
ticket.attach = picture_file
db.session.commit()
flash('Your file has been published.')
return redirect(url_for('ticket', ticket_id=ticket_id))
file = url_for('static', filename='files/' + str(ticket.attach))
return render_template('ticket.html', title=ticket.title,file=file ,ticket=ticket,form=form,comment=com,attachform=attachform)
error is on this line
ticket.attach = picture_file
You are passing ticket_id as a string, when it should be an integer (key):
Try:
#app.route('/ticket/<int:ticket_id>',methods=['GET','POST'])
#login_required
def ticket(ticket_id):
# or use ticket_id = int(ticket_id)
...
I think you should query Tickets with Attachment using outer join.
ticket = db.session.query(Tickets, Attachment).outerjoin(Attachment, Tickets.id == Attachment.tickets_id).get_or_404(ticket_id)
after this query, you need to access table like this.
ticket.Tickets.xxx
ticket.Attachments.xxx
So, you set attachment file to ticket.Attachments.
ticket.Attachments.tickets_id == tickets_id
ticket.Attachments.file = picture_file
if ticket.Attachments is None,
tichet.Attachment = attachment

How do I update table in Flask-SQLAlchemy?

To add the task, I did:
task = TaskList(task_name=form_add_task.task_name.data, doer=current_user) db.session.add(task) db.session.commit()
Now, user and tasks has one to many relationship. What I am trying to do in the UI is display checkbox for each of the task for the specific user(task_status column). If the user selects checkboxes(which could be one or many) and click on update button, I want to change the task_status to 1 by updating it.
How do I just update the task_status to 1 for a specific user if they select one or more checkboxes?
models.py
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(128))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
tasks = db.relationship('TaskList', backref='doer', lazy='dynamic')
class TaskList(db.Model):
id = db.Column(db.Integer, primary_key=True)
task_name = db.Column(db.String(140), nullable=False)
task_status = db.Column(db.Integer, default=0)
date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
forms.py
class AddTaskForm(FlaskForm):
task_name = StringField('task', validators=[DataRequired()])
add_task_submit = SubmitField('Add Task')
class UpdateTaskForm(FlaskForm):
task_status = BooleanField()
update_task_submit = SubmitField('Update Task')
routes.py
#app.route('/mytask', methods=['POST', 'GET'])
#login_required
def my_task():
form_add_task = AddTaskForm()
form_update_task = UpdateTaskForm()
if form_add_task.validate_on_submit():
task = TaskList(task_name=form_add_task.task_name.data, doer=current_user)
db.session.add(task)
db.session.commit()
user = User.query.filter_by(username=current_user.username).first()
task_list = user.tasks.filter_by(task_status=0)
if form_update_task.validate_on_submit():
if form_update_task.task_status.data is True:
**HOW DO I UPDATE THE CHECKBOXES**
return render_template('my_task.html', title='my tasks',
form_add_task=form_add_task,
form_update_task=form_update_task,
task_list=task_list,
user=user
)
Firstly, you need something in your update task form to identify your task. Right now the object only has a boolean field but it has no way to tell flask or sql alchemy on which task should be updated. I would thus add task.id or, following your logic, task.task_name fields to the update form.
class UpdateTaskForm(FlaskForm):
#task_id = IntegerField(#add relevant params here#)
task_name = StringField('task', validators=[DataRequired()])
task_status = BooleanField()
update_task_submit = SubmitField('Update Task')
Afterwards i would use the task.id or task.task_name fields to fetch the task that needs to be updated from the database, set the task_status field to 1 and persist it.
task = TaskList.query.filter_by(id=form_update_task.id).first()
# or
# task = TaskList.query.filter_by(task_name=form_update_task.name).first()
if task is not None:
task.task_status = 1
db.session.add(task)
db.session.commit()

How do I change the default selection for a selectfield using a custom form with Flask-AppBuilder?

Change my Flask app into a Flask-Appbuilder, and running into some issues. I am looking to change the default selection of a SelectField using the parameter in the url, kind of like the automatic forms do. From the following configuration, I get "/rooms/add?_flt_0_building=3" in the URL when the third building is selected. How can I get building_id in the WTForm to select his on the page?
Room form:
class RoomForm(DynamicForm):
building_list = db.session.query(Building).all()
name = StringField('Room name', validators=[DataRequired()])
building_id = SelectField('Building', choices=[(r.id, r.name) for r in building_list], validators=[DataRequired()])
floor_id = StringField('Floor', validators=[DataRequired()])
Room model:
class Room(Model):
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True, nullable=False)
building_id = Column(Integer, ForeignKey('building.id'), nullable=False)
building = relationship("Building")
floors_id = Column(Integer, ForeignKey('floors.id'), nullable=True)
floor = relationship("Floors")
node = relationship('Nodes', backref='roomkey', lazy='dynamic')
device = relationship('Devices', backref='roomkey2', lazy='dynamic')
def __repr__(self):
return self.name
class Building(Model):
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True, nullable=False)
floors = Column(Integer)
gunitbuilding = relationship('Room', backref='buildingkey', lazy='dynamic')
def __repr__(self):
return self.name
return self.id
Room views:
class Rooms(ModelView):
datamodel = SQLAInterface(Room)
show_title = 'Rooms'
add_title = 'Add room'
edit_title = 'Edit room'
list_title = 'Rooms'
list_columns = ['name']
show_fieldsets = [('Summary', {'fields':['name']} )]
search_columns = ['name']
add_columns = ['name','building_id','floor_id']
add_form = RoomForm
class managerooms(MasterDetailView):
datamodel = SQLAInterface(Building)
list_title = 'Buildings'
list_columns = ['name']
related_views = [Rooms]
show_title = 'Buildings'
appbuilder.add_view_no_menu(managerooms)
appbuilder.add_view_no_menu(Rooms)
appbuilder.add_link("Rooms", href="/managerooms/list/1?next=%2Fmanagerooms%2Flist%2F1", icon="", category="Manage")
I figured out a work-around. If there is a better way please let me know. I added refresh() to the RoomForm, and since I am not sure how to get anything other than the full url, parameter string, I subtracted the first 19 characters to get the correct building number.
def refresh():
form = RoomForm()
if request.method == 'GET':
a=len(request.query_string)-19
form.building_id.default = int(request.query_string[-a:])
form.process()
return form

Categories

Resources