I am trying to configure the flask_msearch ES search. I understand that it is not the most popular library but it is quite easy to start with. This is my first time using elasticsearch but I'd like to know if anyone knows how to configure parameter such as fuzziness using this library?
I can search using the query in the w_search function but I am struggling to find how I can configure it. Thank you in advance.
flask_msearch: https://github.com/honmaple/flask-msearch
my code:
class Post(db.Model):
__tablename__ = 'symptom_database'
__searchable__ = ['symptom']
id = db.Column(db.Integer, primary_key=True)
symptom = db.Column(db.String(100), unique=True)
def __repr__(self):
return '<Post %r>' % self.symptom
# views.py
#app.route("/search", methods = ['GET','POST'])
def w_search():
form = Post()
if request.method == 'POST':
keyword = request.form.get('keyword')
results = Post.query.msearch(keyword,fields=['symptom']).all()
print(results, flush=True)
return ''
return render_template('search.html')
ES fuzziness search use keyword regexp
Such as:
Post.query.msearch("{keyword}*".format(keyword=keyword),fields=['symptom']).all()
# or
Post.query.msearch("symptom:{keyword}*".format(keyword=keyword)).all()
Related
I'm trying to append SQLAlchemy's query result to a list.
My application contains following model ("Game)" and games_query (used as FlaskForm's QuerySelectField query_factory).
EDIT: Also added FlaskForm as it seems the problem has to do wi the form itself rather than query_factory.
class Game(db.Model):
game_id = db.Column(db.Integer, primary_key=True, nullable=False)
title = db.Column(db.String, nullable=False, unique=True)
records = db.relationship('Record', backref='game')
def games_query():
games_list = []
for g in Game.query:
games_list.append(g.title)
print(g.title)
return games_list
class ScoreForm(FlaskForm):
name = StringField('Name', validators=[InputRequired()])
score = IntegerField('Score', validators=[InputRequired()])
game = QuerySelectField('Game', query_factory=games_query)
submit = SubmitField('Submit')
games_query returns the following error, despite printing g.title in the console returning valid values (games titles):
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'builtins.str' is not
mapped
EDIT: Setting query_factory as a function returning basic lists results in the same error being displayed.
def games_query():
games_list = ["Mario", "Zelda"]
return games_list
How can I bypass that error sot hat games_query returns the list of the games titles that can be passed as options to relevant form's field?
I've found what caused the error. The solution is below.
The query_factory function shall only return complete query (not list, and not single column of the query):
def games_query():
return db.session.query(Game)
Jinja code related to the FlaskForm by default will display primary_key column, unless will set a specific column in the FlaskForm by using get_label attribute:
class ScoreForm(FlaskForm):
name = StringField('Name', validators=[InputRequired()])
score = IntegerField('Score', validators=[InputRequired()])
game = QuerySelectField('Game', query_factory=games_query, get_label='title')
submit = SubmitField('Submit')
def games_query():
games_list = []
for row in Game.query:
games_list.append(row.__dict__['title'])
print(row.__dict__['title'])
return games_list
Try it
I have a simple search in my Django project. I want to search through documents using their type and part of factory info in addition to search by name.
Here is my models.py:
class Docs(models.Model):
Date = models.DateField(default=date.today)
Name = models.CharField(max_length=50)
Type = models.ForeignKey(DocTypes)
Part = models.ForeignKey(Parts)
Link = models.FileField(upload_to='Docs/%Y/%m/%d')
class Parts(models.Model):
Name = models.CharField(max_length=50)
def __str__(self):
return str(self.Name)
class DocTypes(models.Model):
Type = models.CharField(max_length=50)
def __str__(self):
return str(self.Type)
My forms.py:
class DocsSearchForm(ModelForm):
class Meta:
model = Docs
fields = [ 'Name', 'Type', 'Part']
And this is part of my views.py, if no search was done then all documents are given
def showdocs(request):
if request.method == 'POST':
form = DocsSearchForm(request.POST)
documents = Docs.objects.filter(Name__contains=request.POST['Name']|
Type==request.POST['Type']|
Part==request.POST['Part'])
else:
form = DocsSearchForm()
documents = Docs.objects.all()
return render(
request,
'showdocs.html',
{'documents': documents, 'form':form}
So, the problem is the following: if I try to use a search then I have
NameError at /showdocs
name 'Type' is not defined.
POST values are:Part '1', Name 'Example', Type '1'.
If I delete
Type==request.POST['Type']|
Part==request.POST['Part']
then search by name works well. So I have a guess that problem is about searching by foreign key values, but have no ideas more. Will appreciate any help.
Try replacing the line with this
Docs.objects.filter(Name__contains=request.POST['Name'],
Type=request.POST['Type'],
Part=request.POST['Part']
)
It seems you have misunderstood the syntax. I don't know why you are trying to use | operator here.
That's not how Django filters work. You can't | them because they are not actually expressions, just keyword arguments. In this case, correct syntax would be:
Docs.objects.filter(
Name__contains=request.POST['Name'],
Type_Type=request.POST['Type'],
Part_Name=request.POST['Part'],
)`
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)
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.
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()