Flask-SQLAlchemy adds unnecessary row in parents table (relational tables) - python

I'm recently trying to build a little web-app with Flask. For the database 'stuff' I use Flask-SQLAlchemy and now I'm trying to get a relationship between two objects going.
I have a 'project' table and a 'file' table and it should be a one-to-many relation, so x files can be associated with one project (actually there are more relations coming in the future when I've figured the current problem out).
I've made a input-mask template so a user can upload a file and link it to a project via a dropdown which is populated with the existing projects stored in its table. Thats the corresponding view:
#app.route('/admin/upload/', methods=('GET', 'POST'))
def upload():
form = forms.UploadForm()
if not os.path.isdir(app.config['UPLOAD_FOLDER']):
os.mkdir(app.config['UPLOAD_FOLDER'])
print('Folder created')
form.projectId.choices = []
for g in models.Project.query.order_by('name'):
form.projectId.choices.append((g.id, g.name))
if form.validate_on_submit():
filename = secure_filename(form.fileUpload.data.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
assocProject = models.Project(name=models.Project.query.filter_by(id=form.projectId.data).first().name)
form.fileUpload.data.save(filepath)
prepedFile = models.File(path=filepath, project=assocProject)
print(prepedFile)
print(form.projectId.data)
db.session.add(prepedFile)
db.session.commit()
return 'success'
else:
filename = None
return render_template('upload.html', form=form, filename=filename)
The prepared file should be an instance of the File-Class which has the linked Project-instance as an attribute, therefore the commit should work.
class Project(db.Model):
__tablename__ = 'project'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
file = db.relationship('File', backref="projects")
post = db.relationship('Post', backref="projects")
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Project %r>' % self.name
class File(db.Model):
__tablename__ = 'file'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
path = db.Column(db.String(64))
type = db.Column(db.String(6))
projectId = db.Column(db.Integer, db.ForeignKey('project.id'))
project = db.relationship('Project', backref='files')
def __init__(self, path, project):
self.path = path
self.project = project
fullName = re.search('[a-zA-Z0-9]*\.[a-zA-Z]{2,}', path)
splitName = fullName.group(0).split('.')
self.name = splitName[0]
self.type = splitName[1]
def __repr__(self):
return '<File %r %r %r %r>' % (self.name, self.type, self.path, self.project)
And now the problem: When I try to upload a file it works and the file information are stored in the file table but it creates a new entry in the project table and link its id to the file entry. E.g., if the project entry looks like: name = TestProj1, id=1 and I try to link the uploaded to the project it will create a second project: name = TestProj1, id=2.
Thats my struggle and I cant figure out whats wrong. Maybe some of you now. I appreciate any help!
P.S. Maybe it is relevant, here the form I wrote:
class UploadForm(Form):
fileUpload = FileField(label='Deine Datei')
projectId = SelectField(u'Projekte', coerce=int)

You create a new Project each time.
assocProject = models.Project(name=models.Project.query.filter_by(id=form.projectId.data).first().name)
This does two things. First, it finds the first project with the specified id. Second, it passes that project's name to Project(), creating a new instance using that same name.
What you really want is
assocProject = models.Project.query.get(form.projectId.data)

Related

How to create a route with python and flask based on user input?

I am using python with flask as a backend and I am trying to create a route to a table in my database based on user input. During one of my post requests a table is created in the database. I then want to create a connection to this table. The table name also depends on user input. I want to emphasize that I am able to create the tables in the database based on user input and now need to create the route to these tables.
Is there a way to create such route?
I understand that doing something like this may lead to security vulnerability so I am open to suggestions for different approaches.
I am attaching below my current python code for creating routes:
# Destination Class/model
class Destinations(db.Model):
ID = db.Column(db.Integer, primary_key=True)
city = db.Column(db.String(100))
country = db.Column(db.String(100))
def __init__(self, city, country):
self.city = city
self.country = country
# Destinations Schema
class DestinationsSchema(ma.Schema):
class Meta:
fields = ('ID', 'city', 'country')
# Init Destinations Schema
destination_schema = DestinationsSchema()
destinations_schema = DestinationsSchema(many=True)
# Create a Destination
#app.route('/destinations', methods=['POST'])
def add_destination():
city = request.json['city']
country = request.json['country']
new_destination = Destinations(city, country)
db.session.add(new_destination)
db.session.commit()
return destination_schema.jsonify(new_destination)
# Get All Destinations
#app.route('/destinations', methods=['GET'])
def get_destinations():
all_destinations = Destinations.query.all()
result = destinations_schema.dump(all_destinations)
return jsonify(result)
This is my answer to what I understood from your question. I hope it helps you:
You can put a variable in the url. Something like this:
#app.route('/destinations/<tablename>', methods=['GET'])
def get_destination(tablename):
destination = Destinations.query.filter_by(name=tablename)
result = destinations_schema.dump(destination )
return jsonify(result)

Django not see the table in generic relation

I'm working with Django 1.7.2 with generic relation (reason: project has two databases), and after import I don't want to have overwritten database.
I want to show only active season
Here is my model, is overwritten after import:
class Season(models.Model):
name = models.CharField('Name', max_length=255)
start = models.DateField('Start')
end = models.DateField('End')
def __unicode__(self):
return self.name
In another place in models.py file I create Generic relation to another model (SaleAndCycle) in another database:
def extend_models(sender, **kwargs):
if sender._meta.app_label != 'extend':
return
def set_stati(self, database='extend', save=True):
online_before = self.online
self.online = True
for flag in self.flags:
if flag.flag == 'offline':
self.online = False
if save and self.online != online_before:
self.save(using=database)
for name, name_singular, name_plural in (
('Season', 'Season', 'Seasons'):
if sender.__name__ == name:
sender._meta.verbose_name = name_singular
sender._meta.verbose_name_plural = name_plural
if sender.__name__ == 'Season':
sender.add_to_class('sale_and_cycles', generic.GenericRelation(SaleAndCycle))
sender.add_to_class('is_sale_active', property(
lambda o: o.sale_and_cycles.using('default')))
sender.add_to_class('is_cyclic_event_active', property(
lambda o: o.sale_and_cycles.using('default')))
sender.add_to_class('cycle_link', property(
lambda o: o.sale_and_cycles.using('default')))
I want to show all active Seasons for not login user:
def get_queryset(self):
seasons = Season.objects.all()
if not self.request.user.is_superuser:
all_seasons = Season.objects.filter(events_isactiveflags__is_active=True)
print all_seasons
I get error:
no such table: events_isactiveflag
But this table exists in my database.
by the line of code
Season.objects.filter(events_isactiveflags__is_active=True)
here events_isactiveflags__is_active=True means events_isactiveflags is a table and is_active is a column of the table. So no such table found named events_isactiveflags . if you are trying to get active sessions you can try this code
Session.objects.filter(expire_date__gte=timezone.now())
delete db and once again run syncdb

Part of Data get lost on adding to session and commiting flask

The model is
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
artistname = db.Column(db.String(64))
photourl = db.Column(db.String(1000))
contactInfo = db.Column(db.String(20))
description = db.Column(db.String(500))
date = db.Column(db.Date)
def __repr__(self):
return '<User %r>' % (self.photourl)
Here photourl is the url of photos posted.
After form submission.
user = User(artistname = form.artist.data,photourl = "",
description = form.description.data,contactInfo = form.contactinfo.data,date = datetime.datetime.utcnow().date() )
I add all the details without photourl.
Now i make list of all the filenames which is stored in filename variable in below code.And join with * in middle.
filename = "*".join(filename)
print(filename)
The sample output appeared in terminal of printed filename is
mic16.jpg*nepal_earthquake_death6.png
After combining all the filenames. I store it in database by.
user.photourl = filename
print(user)
db.session.add(user)
db.session.commit()
Here printed output of user in terminal is
<User u'mic16.jpg*nepal_earthquake_death6.png'>
which shows that infomation is loaded correctly.
Now when I do db.session.add(user) followed by db.session.commit(). In user table of the database under photourl column only mic16.jpg part is stored and rest of the part is ommited i.e. part before * is stored.
There is no entry in the database.My database if a MYSQL database and using phpmyadmin. I am reading the database by using.
posts = User.query.order_by(User.date.desc()).limit(5).all()
photourls = []
for i in posts:
photourls.append(i.photourl.split('*'))
Required urls are to be in the photourls. But only a single url is present for each post.
I am just out of my mind and don't have clue of whats going on?
Judging by the size of your photourl string, you want to save several image filenames inside a string separated by an asterisk *. A better alternative would be storing the filenames in a JSON array with each filename as a string.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
artistname = db.Column(db.String(64))
photourls = db.Column(JSON)
You can use getlist to upload several image files at once.
def upload():
uploaded_images = flask.request.files.getlist("file")
The JSON would be stored as shown below.
{
"photourls":["mic16.jpg", "nepal_earthquake_death6.png"]
}

Populate a WTForms form object with a datetime.date

I'm cooking up a crud interface for an object representing a bill, as in the water bill, the electric bill, etc.
I'm using sqlalchemy to handle the data, wtforms to handle the forms, and flask to serve it.
Here's what my route looks like that serves the form for editing an existing bill:
#app.route('/edit_bill/<int:bill_id>', methods = ['GET'])
def edit_bill(bill_id):
s = Session()
bill = s.query(Bill).filter_by(id=bill_id).first()
form = BillForm(obj=Bill)
return render_template('edit_bill.html', form = form)
Using wtforms, I pass the bill object to the BillForm constructor, ensuring that the data representing the bill to be edited it populated to the form.
This is where it chokes. Here's the exception:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Bill.date_due has an attribute 'strftime'
Now, I've dipped into the python shell and queried up a bill to make sure that date_due has a datetime.date object on it, which is does. I use Jinja to build my front end, so I've looked into creating a template filter, but I don't know how that would work with wtforms, and it looks like sqlalchemy is the one choking anyway.
So what it do? I'm pretty confident I just need to figure out how to turn that datetime.date object into a string, but I'm not sure how to go about that.
Halp. Thanks!
Edit: Here's the BillForm class:
class BillForm(Form):
id = HiddenField()
name = TextField(u'Name:', [validators.required()])
pay_to = TextField(u'Pay To:',[validators.required()])
date_due = DateField(u'Date Due:',[validators.required()])
amount_due = IntegerField(u'Amount Due:', [validators.required()])
date_late = DateField(u'Late After:',[validators.required()])
amount_late = IntegerField(u'Late Amount:', [validators.required()])
date_termination = DateField(u'Termination Date:',[validators.required()])
And mapping class, too:
class Bill(Base):
__tablename__ = 'bills'
id = Column(Integer, primary_key=True)
name = Column(String)
pay_to = Column(String)
amount_due = Column(Integer)
date_due = Column(Date)
amount_late = Column(Integer)
date_late = Column(Date)
date_termination = Column(Date)
def __init__(self, name, pay_to, amount_due, date_due, amount_late, date_late, date_termination):
self.name = name
self.pay_to = pay_to
self.amount_due = amount_due
self.date_due = date_due
self.amount_late = amount_late
self.date_late = date_late
self.date_termination = date_termination
def __repr__(self):
return "<Bill ('%s', '%s', '%s', '%s')>" % (self.name, self.pay_to, self.amount_due, self.date_due)
Ah it took me some time to figure out where you went wrong, but think I found it out. Here's your code:
#app.route('/edit_bill/<int:bill_id>', methods = ['GET'])
def edit_bill(bill_id):
s = Session()
bill = s.query(Bill).filter_by(id=bill_id).first()
form = BillForm(obj=Bill)
return render_template('edit_bill.html', form = form)
Now, if pass a class as the obj kwarg in BillForm, the form gets populated with all kinds of strange objects. For example, if I replicate what you did and inspect form.date_due.data, it says it is an <sqlalchemy.orm.attributes.InstrumentedAttribute at 0x277b2d0> object. Like what is stated in the error message, this object does not have a strftime attribute.
So, your error is in line 5 of the code you presented. If you want to populate the form with the details of the bill object you retrieved in line 4, replace line 5 with form = BillForm(obj=bill). As you can see, the 'subtle' difference is the lowercase b in bill. I replicated your code and am convinced should fix the problem.
If you're interested, this is how I normally make edit views.
#app.route('/edit_bill/<int:bill_id>', methods = ['GET', 'POST'])
def edit_bill(bill_id):
s = Session()
bill = s.query(Bill).filter_by(id=bill_id).first()
form = BillForm(request.form, obj=bill)
if request.method == 'POST' and form.validate():
form.populate_obj(bill)
s.add(bill)
s.commit()
# Do some other stuff, for example set a flash()
return render_template('edit_bill.html', form = form)
I haven't used SQLAlchemy for a while so I might have made a couple of mistakes there. Hope this helps! If this answers your question 'accept' the answer.

Flask-SQLalchemy update a row's information

How can I update a row's information?
For example I'd like to alter the name column of the row that has the id 5.
Retrieve an object using the tutorial shown in the Flask-SQLAlchemy documentation. Once you have the entity that you want to change, change the entity itself. Then, db.session.commit().
For example:
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
db.session.commit()
user = User.query.get(5)
user.name = 'New Name'
db.session.commit()
Flask-SQLAlchemy is based on SQLAlchemy, so be sure to check out the SQLAlchemy Docs as well.
There is a method update on BaseQuery object in SQLAlchemy, which is returned by filter_by.
num_rows_updated = User.query.filter_by(username='admin').update(dict(email='my_new_email#example.com')))
db.session.commit()
The advantage of using update over changing the entity comes when there are many objects to be updated.
If you want to give add_user permission to all the admins,
rows_changed = User.query.filter_by(role='admin').update(dict(permission='add_user'))
db.session.commit()
Notice that filter_by takes keyword arguments (use only one =) as opposed to filter which takes an expression.
This does not work if you modify a pickled attribute of the model. Pickled attributes should be replaced in order to trigger updates:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from pprint import pprint
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqllite:////tmp/users.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.PickleType())
def __init__(self, name, data):
self.name = name
self.data = data
def __repr__(self):
return '<User %r>' % self.username
db.create_all()
# Create a user.
bob = User('Bob', {})
db.session.add(bob)
db.session.commit()
# Retrieve the row by its name.
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Modifying data is ignored.
bob.data['foo'] = 123
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Replacing data is respected.
bob.data = {'bar': 321}
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
# Modifying data is ignored.
bob.data['moo'] = 789
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
Just assigning the value and committing them will work for all the data types but JSON and Pickled attributes. Since pickled type is explained above I'll note down a slightly different but easy way to update JSONs.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.JSON)
def __init__(self, name, data):
self.name = name
self.data = data
Let's say the model is like above.
user = User("Jon Dove", {"country":"Sri Lanka"})
db.session.add(user)
db.session.flush()
db.session.commit()
This will add the user into the MySQL database with data {"country":"Sri Lanka"}
Modifying data will be ignored. My code that didn't work is as follows.
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
db.session.merge(user)
db.session.flush()
db.session.commit()
Instead of going through the painful work of copying the JSON to a new dict (not assigning it to a new variable as above), which should have worked I found a simple way to do that. There is a way to flag the system that JSONs have changed.
Following is the working code.
from sqlalchemy.orm.attributes import flag_modified
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
flag_modified(user, "data")
db.session.merge(user)
db.session.flush()
db.session.commit()
This worked like a charm.
There is another method proposed along with this method here
Hope I've helped some one.
Models.py define the serializers
def default(o):
if isinstance(o, (date, datetime)):
return o.isoformat()
def get_model_columns(instance,exclude=[]):
columns=instance.__table__.columns.keys()
columns=list(set(columns)-set(exclude))
return columns
class User(db.Model):
__tablename__='user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
.......
####
def serializers(self):
cols = get_model_columns(self)
dict_val = {}
for c in cols:
dict_val[c] = getattr(self, c)
return json.loads(json.dumps(dict_val,default=default))
In RestApi, We can update the record dynamically by passing the json data into update query:
class UpdateUserDetails(Resource):
#auth_token_required
def post(self):
json_data = request.get_json()
user_id = current_user.id
try:
instance = User.query.filter(User.id==user_id)
data=instance.update(dict(json_data))
db.session.commit()
updateddata=instance.first()
msg={"msg":"User details updated successfully","data":updateddata.serializers()}
code=200
except Exception as e:
print(e)
msg = {"msg": "Failed to update the userdetails! please contact your administartor."}
code=500
return msg
I was looking for something a little less intrusive then #Ramesh's answer (which was good) but still dynamic. Here is a solution attaching an update method to a db.Model object.
You pass in a dictionary and it will update only the columns that you pass in.
class SampleObject(db.Model):
id = db.Column(db.BigInteger, primary_key=True)
name = db.Column(db.String(128), nullable=False)
notes = db.Column(db.Text, nullable=False)
def update(self, update_dictionary: dict):
for col_name in self.__table__.columns.keys():
if col_name in update_dictionary:
setattr(self, col_name, update_dictionary[col_name])
db.session.add(self)
db.session.commit()
Then in a route you can do
object = SampleObject.query.where(SampleObject.id == id).first()
object.update(update_dictionary=request.get_json())
Update the Columns in flask
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
admin.save()
To use the update method (which updates the entree outside of the session) you have to query the object in steps like this:
query = db.session.query(UserModel)
query = query.filter(UserModel.id == user_id)
query.update(user_dumped)
db.session.commit()

Categories

Resources