Flask: how to add related One-to-Many models from forms - flask

I have two models with One-to-Many relations
first_model
class Datacenter(db.Model):
id = db.Column(db.Integer, primary_key=True)
number = db.Column(db.Integer)
name = db.Column(db.String(64), index=True, unique=True)
place = db.Column(db.String(64))
capacity = db.Column(db.Integer, index=True)
server = db.relationship('Server', backref='datacenter', lazy=True)
tier = db.Column(db.Integer)
def __repr__(self):
return '<Datacenter {}>'.format(self.name)
second_models
class Server(db.Model):
id = db.Column(db.Integer, primary_key=True)
number = db.Column(db.Integer)
name = db.Column(db.String(64), index=True, unique=True)
factory = db.Column(db.String(64), index=True)
model = db.Column(db.String(64))
serial = db.Column(db.Integer)
os = db.Column(db.String(64))
datacener_id=db.Column(db.Integer, db.ForeignKey('datacenter.id'), nullable=False)
def __repr__(self):
return '<Server {}>'.format(self.name)
However, I'm not sure that I correctly described the relationship between them in the fields of the model.
All I need is to create a new Server model with a connection to the Datacenter model.
I think that this can be done using the select field in the form. so I created a form
class ServerForm(FlaskForm):
number = IntegerField('Number')
name = StringField('Name')
factory = StringField('Factory')
model = StringField('Model')
serial = IntegerField('Model')
os = StringField('OS')
datacener_id = SelectField('Datacener', choices=[ ???? ])
and router
#app.route('/add_server', methods = ['POST', 'GET'])
def add_server():
form = ServerForm(request.form)
if request.method=='POST' and form.validate():
data = Server(
number=form.number.data,
name=form.name.data,
factory=form.factory.data,
model=form.model.data,
serial=form.serial.data,
os=form.os.data,
datacener_id=form.datacener_id.data
)
db.session.add(data)
db.session.commit()
flash('Server created successfully!')
return redirect('/')
return render_template('new_server.html', form=form)
But when I go to the page for adding a new model, the drop-down list is empty. Tell me what I need to change in my code

Related

Can't get Association table to eager load with Movie for particlular User, to show if the User who added the movie is being followed or not Flask

class Movie(db.Model):
__searchable__ = ['genre']
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
username = db.Column(db.String(255))
description = db.Column(db.String(100))
class User(db.Model,UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
movies = db.relationship('Movie', backref='author', lazy='joined')
followed = db.relationship('User', secondary=followers,
primaryjoin=(followers.c.follower_id==id),
secondaryjoin=(followers.c.followed_id==id),
backref=db.backref('followers', lazy='joined'), lazy='joined')
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id'), primary_key = True),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'), primary_key = True) )
def get_all_movies_follow():
qry = Movie.query.order_by(Movie.id.desc()).all()
movies_schema = MovieSchema(many=True)
return movies_schema.dump(qry)
How to get back an collection (array) where I have a property that shows whether
When I eager_load followers table it's still won't show up in the query.
``` #movi = Movie.query.outerjoin(followers, followers.c.followed_id == Movie.user_id).options(contains_eager( Movie.us).contains_eager( 'followers')).order_by(Movie.id.desc()).all()
Also when I try to use follow unfollow function I get Select statement 'SELECT *
FROM followers, movie AS movie_1
WHERE followers.followed_id = movie_1.user_id' returned no FROM clauses due to auto-correlation; specify correlate(<tables>) to control correlation manually.
which in short is def unfollow(id):
...
current_user.unfollow(user)
db.session.commit()
def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)
def is_following(self, user):
return self.query.filter(followers.c.followed_id==user.id).count()>0
So I tried adding this to Movie class: ```following = column_property( exists().where(followers.c.followed_id==user_id)) but it has to be also restricted on current_user.id=followers.c.follower_id
I am thinking maybe statement that will be included when I query for the Movie
ok, so this works, granted that I included following in the schema to dump on MovieSchema.
But it has a problem. If there are no matches to (followers.c.follower_id) so user is not following anyone. then I get an empty result and no movie gets loaded at all.
class Movie(db.Model)
...
following = column_property( exists().where(followers.c.followed_id==user_id).correlate_except(followers))
and in query
qry = db.session.query(Movie).filter(followers.c.follower_id == current_user.id).all()

Flask: Querying inherited classes from SQLAlchemy brings different results in two similar contexts

I am working on a project with two different users: client, employee. Clients post and Employees confirm the posts or not. To do this I have on User class which deals mostly with authentication and basic procedures, and both the Client and Employee class inherit from it.
Practically:
class User(UserMixin, db.Model):
__tablename__ = 'user'
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_hash = db.Column(db.String(128))
user_type = db.Column(db.String(32), nullable=False)
__mapper_args__ = {'polymorphic_identity': user_type}
class Client(User):
__tablename__ = 'client'
__mapper_args__ = {'polymorphic_identity':'client'}
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
telephone = db.Column(db.String(120))
birthday = db.Column(db.String(120))
posts = db.relationship('Post', backref='author', lazy='dynamic')
class Employee(User):
__tablename__ = 'employee'
__mapper_args__ = {'polymorphic_identity':'employee'}
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
employee_extra = db.Column(db.String(140))
When I want to get all the clients in one of my routes which is something like:
#bp.route('/posts', methods=['GET', 'POST'])
#login_required
def posts():
clients = Client.query.all()
print(clients[0].birthday)
I get results like: AttributeError: 'User' object has no attribute 'birthday'. because the result is [<User 2>].
On the other hand, when I set up an environment for testing, and I run the same query.
db_app.setUp()
# self.app = create_app(Config)
# self.app_context = self.app.app_context()
# self.app_context.push()
# db.create_all()
# self.client = self.app.test_client()
# db.session.commit()
db_app.create_user('11123123123','user#gmail.com','123123123','client')
print(Client.query.all())
It brings the necessary results: [<Client 2>]
I have read on different sources, and main documentation of SQLAlchemy, but cant really see where this difference comes from. What am I missing?

Submitting a form with SQLAlchemy throws 'list' object has no attribute

I am new both to Flask and SQLAlchemy and to coding, so have patience please.
What I am trying to do is to send data through a form to my database.
Worked fine until I wanted two more tables with relationship one to many, as in a plant can accumulate many elements and a plant can have many properties and flask throws error AttributeError: 'list' object has no attribute '_sa_instance_state'.
The form is :
from flask_wtf import FlaskForm
from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
from wtforms import StringField, PasswordField, SubmitField,BooleanField, TextAreaField
#Query for Dynamic Nutrient Accumulator Model
def enabled_dna():
return DNA.query.all()
#Query for Nitrogen Fixers Nursing Model
def enabled_nfn():
return NFN.query.all()
class NewPlantForm(FlaskForm):
common_name = StringField('Common Name', render_kw={"placeholder": "Common name"},
validators=[DataRequired(), Length(min=2, max=40)])
botanical_name = StringField('Botanical Name', render_kw={"placeholder": "Botanical name"},
validators=[DataRequired(), Length(min=2, max=80)])
short_description = TextAreaField('Short Description', render_kw={"placeholder": "Please add a short description"},
validators=[DataRequired()])
medicinal = TextAreaField('Medicinal Use', render_kw={"placeholder": "Medicinal use"},
validators=[DataRequired()])
dna = QuerySelectMultipleField('Select Element',query_factory=enabled_dna,allow_blank=True)
nfn = QuerySelectMultipleField('Select Property',query_factory=enabled_nfn,allow_blank=True)
submit = SubmitField('Add plant')
The models.py looks like this :
#Plants Table
class Plants(db.Model):
id = db.Column(db.Integer, primary_key=True)
common_name = db.Column(db.String(40), nullable=False)
botanical_name = db.Column(db.String(80), unique=True, nullable=False)
short_description = db.Column(db.Text, nullable=False)
medicinal = db.Column(db.Text, nullable=False)
image_file = db.Column(db.String(20), default='default_plant_pic.jpg')
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
dna_id = db.Column(db.Integer, db.ForeignKey('DNA.id'))
dna = db.relationship('DNA', backref=db.backref('plant_dna', lazy='dynamic')) # Dynamic_Nutrient_Accumulated
nfn_id = db.Column(db.Integer, db.ForeignKey('NFN.id'))
nfn = db.relationship('NFN', backref=db.backref('plant_nfn', lazy='dynamic')) # Nitrogen_Fixers_Nursing
def __repr__(self):
return f"Plants('{self.common_name}', '{self.botanical_name}', '{self.short_description}'," \
f" '{self.medicinal}', '{self.dna}', '{self.nfn}' )"
#Dynamic_Nutrient_Accumulated
class DNA(db.Model):
id = db.Column(db.Integer, primary_key=True)
element = db.Column(db.String(15))
def __repr__(self):
return '[ {}]'.format(self.element)
#Nitrogen_Fixers_Nursing
class NFN(db.Model):
id = db.Column(db.Integer, primary_key=True)
plant_extra = db.Column(db.String(40))
def __repr__(self):
return '[ {}]'.format(self.plant_extra)
The route and form worked fine with the form containing fields only for one table. However, it doesn't work now while I added second and third fields containing data from other tables(form.dna.data and form.nfn.data).
My route for New Plant is :
#app.route("/plants/new/", methods=['GET', 'POST'])
#login_required# User must be logged in to create a new plant
def new_plant():
form = NewPlantForm()
if form.validate_on_submit():
new_plant = Plants(common_name=form.common_name.data,
botanical_name=form.botanical_name.data,
short_description=form.short_description.data,
medicinal=form.medicinal.data,
dna=form.dna.data,
nfn=form.nfn.data,
author=current_user)
db.session.add(new_plant)
db.session.commit()
flash('Thank you ! You have successfully added a plant '
'to the database!', 'success')
return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
image_file=image_file, form=form)
And route where to render the information of plants is :
#app.route("/plants")
def plants():
plants = Plants.query.all()
return render_template('plants.html', title= 'Plants Database', plants=plants)
I have tried working with this locally from terminal and it works, but I don't know what I'm missing or if the relationship model is wrong to get it working from the flask app.
Thanks in advance for patience and help.
Update
After trial and error, it now seems to work all of it (add a plant with the selected fields to the DB, render the plant data correctly into the template, the plant is added correctly to the DB-viewing with DB Browser for SQLite) after I've changed QuerySelectMultipleField to QuerySelectField. However, my point was to be able to select and render multiple choices.
Another thing that I have noticed is that when using QuerySelectField, the template renders correctly a dropdown, but when trying to use QuerySelectMultipleField, it renders just a list with elements, no dropdown.
Here is the small part of the template with the select field form.dna and form.nfn:
<div class="form-group">
{{ form.dna(class="form-control form-control-sm") }}
</div>
<div class="form-group">
{{ form.nfn(class="form-control form-control-sm")}}
</div>
I am using Bootstrap. Could this thing be related to the template formatting not correctly wrote for the multiple select?
Thanks.
Update2
I managed to get QuerySelectMultipleField to work by looping through the form data like this :
#app.route("/plants/new/", methods=['GET', 'POST'])
#login_required# User must be logged in to create a new plant
def new_plant():
form = NewPlantForm()
if form.validate_on_submit():
new_plant = Plants(common_name = form.common_name.data, botanical_name = form.botanical_name.data,
short_description = form.short_description.data, medicinal=form.medicinal.data,
author=current_user)
**for dna_element in form.dna.data:
new_plant.dna = dna_element
for nfn_element in form.nfn.data:
new_plant.nfn = nfn_element**
db.session.add(new_plant)
db.session.commit()
flash(f'Thank you ! You have successfully added a plant to the database!', 'success')
return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
image_file=image_file, form=form)
I don't get anymore the error AttributeError: 'list' object has no attribute '_sa_instance_state' and the plant is successful added to the database, however when I'm looking in the database, I can see that only one option was selected, not multiple choices.
From what I've read here : Flask App Using WTForms with SelectMultipleField , I was supposed to use form.something.data to get a list of items which I did, but still doesn't work and I only get one item.
Please help.
Thanks !
Update 3 and solving the problem
After implementing sleblanc's response, I now have the following code that works with the form and displays correctly:
**models.py : **
plants_dna_table = db.Table(
'plants_dna',
db.Column('plants_id', db.Integer, db.ForeignKey('plants.id'), nullable=False),
db.Column('dna_id', db.Integer, db.ForeignKey('DNA.id'), nullable=False),
db.UniqueConstraint('plants_id', 'dna_id'))
plants_nfn_table = db.Table(
'plants_nfn',
db.Column('plants_id', db.Integer, db.ForeignKey('plants.id'), nullable=False),
db.Column('nfn_id', db.Integer, db.ForeignKey('NFN.id'), nullable=False),
db.UniqueConstraint('plants_id', 'nfn_id'))
#Plants Table
class Plants(db.Model):
id = db.Column(db.Integer, primary_key=True)
common_name = db.Column(db.String(40), nullable=False)
botanical_name = db.Column(db.String(80), unique=True, nullable=False)
short_description = db.Column(db.Text, nullable=False)
medicinal = db.Column(db.Text, nullable=False)
image_file = db.Column(db.String(20), default='default_plant_pic.jpg')
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
dna = db.relationship('DNA', secondary = plants_dna_table) # Dynamic_Nutrient_Accumulated
nfn = db.relationship('NFN', secondary = plants_nfn_table) # Nitrogen_Fixers_Nursing
def __repr__(self):
return f"Plants('{self.common_name}', '{self.botanical_name}', '{self.short_description}'," \
f" '{self.medicinal}', '{self.dna}', '{self.nfn}' )"
#Dynamic_Nutrient_Accumulated
class DNA(db.Model):
id = db.Column(db.Integer, primary_key=True)
element = db.Column(db.String(15))
def __repr__(self):
return '{}'.format(self.element)
#Nitrogen_Fixers_Nursing
class NFN(db.Model):
id = db.Column(db.Integer, primary_key=True)
plant_extra = db.Column(db.String(40))
def __repr__(self):
return '{}'.format(self.plant_extra)
The db.ForeignKey('DNA.id') displayed with capital letters does the job and will not get the error for not finding the table DNA.
**routes.py : **
#Route for users to add a plant to the database
#app.route("/plants/new/", methods=['GET', 'POST'])
#login_required# User must be logged in to create a new plant
def new_plant():
form = NewPlantForm()
if form.validate_on_submit():
new_plant = Plants(common_name = form.common_name.data, botanical_name = form.botanical_name.data,
short_description = form.short_description.data, medicinal=form.medicinal.data,
author=current_user)
for dna_element in form.dna.data:
new_plant.dna.append(dna_element)
for nfn_element in form.nfn.data:
new_plant.nfn.append(nfn_element)
print(new_plant)
db.session.add(new_plant)
db.session.commit()
flash(f'Thank you ! You have successfully added a plant to the database!', 'success')
return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
image_file=image_file, form=form)
Thanks #sleblanc!
You have modeled your relationships in reverse.
You have a One-To-Many relationship from your Plants to your DNA and NFN if I am not mistaken, meaning that one 'Plants' object will have several DNA, and several NFN. As defined, your 'Plants' model only has a single field for DNA and a single field for NFN, meaning that there is no way to store the association in the database.
You will need to modify your Plants model to represent the relationship. You must first determine if a 'DNA' or a 'NFN' can be shared by multiple 'Plants', or if they are unique for each 'Plants' instance, as it involves a different schema.
class Plants(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
# second option, exclusive relationship
class DNA(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
plants_id = db.Column(db.ForeignKey(Plants.id), nullable=False)
...
class NFN(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
plants_id = db.Column(db.ForeignKey(Plants.id), nullable=False)
...
# first option, non-exclusive relationship
class Plants(db.Model):
...
dna = relationship("DNA", secondary=plants_dna_table)
nfn = relationship("NFN", secondary=plants_nfn_table)
plants_dna_table = db.Table(
'plants_dna',
db.Column('plants_id', db.ForeignKey('plants.id'), nullable=False),
db.Column('dna_id', db.ForeignKey('dna.id'), nullable=False),
db.UniqueConstraint('plants_id', 'dna_id'),
)
plants_nfn_table = db.Table(
'plants_nfn',
db.Column('plants_id', db.ForeignKey('plants.id'), nullable=False),
db.Column('nfn_id', db.ForeignKey('nfn.id'), nullable=False),
db.UniqueConstraint('plants_id', 'nfn_id'),
)
After having modified the Plants model and setup a relationship between the two, keep in mind that the .dna and .nfn attributes will have a list type. You can add a relationship to the instances by appending to the list.
I highly recommend you use FlaskForm.populate_obj:
#app.route("/plants/new/", methods=['GET', 'POST'])
#login_required# User must be logged in to create a new plant
def new_plant():
obj = Plants()
form = NewPlantForm()
if form.validate_on_submit():
form.populate_obj(obj)
print(obj) # debugging
db.session.add(obj)
db.session.commit()
flash('Thank you ! You have successfully added '
'a plant to the database!', 'success')
return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
image_file=image_file, form=form)
Also, it lets you combine a future Plant update view by adding just a few more lines:
#app.route('/plant/<id>/edit', methods=('GET', 'POST',))
#app.route('/plant/new', methods=('GET', 'POST'))
def create_plant(id=None):
if id is not None:
obj = Plant.query.get(id)
else:
obj = Plant()
form = PlantForm(request.form, obj=obj)
if form.validate_on_submit():
form.populate_obj(obj)
db.session.add(obj)
db.session.commit()
flash('Thank you ! You have successfully added '
'a plant to the database!', 'success')
return redirect(url_for('get_plant', id=obj.id))
else:
image_file = url_for(
'static',
filename='img/plants/default_plant_pic.jpg')
return render_template('plant.html', title='Add new plant',
image_file=image_file,
form=form, obj=obj)

Select many to many using function count()

I have many to many relationships and i try to find User which has a minimum requests im my subs table but i can't understand how i can do it.
Could you please clarify how i can do it
my Models are:
subs = db.Table('subs',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('request_id', db.Integer, db.ForeignKey('request.id'))
)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120))
role = db.Column(db.String(120))
password_hash = db.Column(db.String(120))
requests = db.relationship('Request', secondary=subs,
backref=db.backref('users', lazy='dynamic'))
post = db.relationship('Posts', backref = 'user', lazy = 'dynamic')
request = db.relationship('Request', backref='user', lazy = 'dynamic')
is_active = db.Column(db.String(120))
class Request(db.Model):
__tablename__ = 'request'
id = db.Column(db.Integer, primary_key=True)
org = db.Column(db.String(120))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
cost = db.Column(db.Integer)
created = db.Column(db.DateTime, default= datetime.utcnow)
cost_time = db.Column(db.Integer)
update_time = db.Column(db.DateTime, default = datetime.utcnow())
diff_time = db.Column(db.DateTime)
feedback = db.Column(db.Text, default=update_time)
comment = db.relationship('Posts', backref = 'request', lazy='dynamic')
rate_idea = db.Column(db.Integer)
new = db.Column(db.Text)
cost_buyer = db.relationship('Costs', backref = 'request', lazy='dynamic')
status = db.Column(db.String(120), db.ForeignKey('status.id'))
For example:
User1.requests = [Request_1, 'Request_2, Request_3]
User2.requests = [Request_2, Request_3]
When somebody do a new Request i need to clarify firstly which user has a minimum requests from all of users and then put this request to him.
New_request = Request(org = 'TEST')
In this case User2 must add this New_request to his own User.requests so the final result must be
User1.requests = [Request_1, 'Request_2, Request_3]
User2.requests = [Request_2, Request_3, New_request]
i want to do query something like this, but what is the right and simple solution for this i don't know and i want to know:
db.query.filter(min(len(User.requests))
Something like this should work. But I suggest you to check the docs.
from sqlalchemy import func
db.query(User.id, func.count())
.outerjoin(User.requests)
.group_by(User.id)
.order_by(func.count())
.limit(1)

Long loading data in flask-admin relationship dropdown input

How to reduce the amount of data in the related table when editing and inserting item form in Flask-admin.
In my related tables more than 500,000 records ...
And the flask-admin uploads all the data in the associated drop-down input (example film).... and can not do it.
I created my model mysql innodb through SQLAlchemy.
I can load the data in the parts table? or disable the loading of related data
class LfMailerQueue(db.Model):
__tablename__ = 'lf_mailer_queue'
mail_id = db.Column(db.Integer, primary_key=True)
template_id = db.Column(db.ForeignKey(u'lf_mailer.id'))
user_id = db.Column(db.ForeignKey(u'modx_user_attributes.internalKey'), index=True)
film_id = db.Column(db.ForeignKey(u'modx_lfvideo_items.id'), index=True)
email = db.Column(db.String(255))
fullname = db.Column(db.String(255))
status = db.Column(db.Integer, nullable=False, server_default=db.FetchedValue())
film = db.relationship(u'ModxLfvideoItem', primaryjoin='LfMailerQueue.film_id == ModxLfvideoItem.id', backref=u'lf_mailer_queues')
template = db.relationship(u'LfMailer', primaryjoin='LfMailerQueue.template_id == LfMailer.id', backref=u'lf_mailer_queues')
user = db.relationship(u'ModxUserAttribute', primaryjoin='LfMailerQueue.user_id == ModxUserAttribute.internalKey', backref=u'lf_mailer_queues')
class ModxLfvideoItem(db.Model):
__tablename__ = 'modx_lfvideo_items'
id = db.Column(db.Integer, primary_key=True)
group = db.Column(db.String(255), nullable=False, unique=True)
def __str__(self):
return self.group
....
class ModxUserAttribute(db.Model):
....