Let's say I have a model like this:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
hometown = db.Column(db.String(140))
university = db.Column(db.String(140))
To get a list of users from New York, this is my query:
User.query.filter_by(hometown='New York').all()
To get a list of users who go to USC, this is my query:
User.query.filter_by(university='USC').all()
And to get a list of users from New York, and who go to USC, this is my query:
User.query.filter_by(hometown='New York').filter_by(university='USC').all()
Now, I would like to dynamically generate these queries based on the value of a variable.
For example, my variable might look like this:
{'hometown': 'New York'}
Or like this:
{'university': 'USC'}
... Or even like this:
[{'hometown': 'New York'}, {'university': 'USC'}]
Can you help me out with writing a function which takes a dictionary (or list of dictionaries) as an input, and then dynamically builds the correct sqlalchemy query?
If I try to use a variable for the keyword, I get this err:
key = 'university'
User.query.filter_by(key='USC').all()
InvalidRequestError: Entity '<class 'User'>' has no property 'key'
Secondly, I am not sure how to chain multiple filter_by expressions together dynamically.
I can explicitly, call out a filter_by expression, but how do I chain several together based on a variable?
Hope this makes more sense.
Thanks!
SQLAlchemy's filter_by takes keyword arguments:
filter_by(**kwargs)
In other words, the function will allow you to give it any keyword parameter. This is why you can use any keyword that you want in your code: SQLAlchemy basically sees the arguments a dictionary of values. See the Python tutorial for more information on keyword arguments.
So that allows the developers of SQLAlchemy to receive an arbitrary bunch of keyword arguments in a dictionary form. But you're asking for the opposite: can you pass an arbitrary bunch of keyword arguments to a function?
It turns out that in Python you can, using a feature called unpacking. Simply create the dictionary of arguments and pass it to the function preceded by **, like so:
kwargs = {'hometown': 'New York', 'university' : 'USC'}
User.query.filter_by(**kwargs)
# This above line is equivalent to saying...
User.query.filter_by(hometown='New York', university='USC')
filter_by(**request.args) doesn't work well if you have non-model query parameters, like page for pagination, otherwise you get errors like these:
InvalidRequestError: Entity '<class 'flask_sqlalchemy.MyModelSerializable'>' has no property 'page'
I use something like this which ignores query parameters not in the model:
builder = MyModel.query
for key in request.args:
if hasattr(MyModel, key):
vals = request.args.getlist(key) # one or many
builder = builder.filter(getattr(MyModel, key).in_(vals))
if not 'page' in request.args:
resources = builder.all()
else:
resources = builder.paginate(
int(request.args['page'])).items
Considering a model with a column called valid, something like this will work:
curl -XGET "http://0.0.0.0/mymodel_endpoint?page=1&valid=2&invalid=whatever&valid=1"
invalid will be ignored, and page is available for pagination and best of all, the following SQL will be generated: WHERE mymodel.valid in (1,2)
(get the above snippet for free if you use this boilerplate-saving module)
As pointed out by #opyate that filter_by(**request.args) doesn't work well if you have non-model query parameters, like page for pagination, the following alternative can be used too:
Assuming that page is being taken in the form of request.args.get(), then:
def get_list(**filters):
page = None
if 'page' in filters:
page = filters.pop('limit')
items = Price.query.filter_by(**filters)
if page is not None:
items = items.paginate(per_page=int(page)).items
else:
items = items.all()
return {
"items": items
}
and then the get function
def get(self):
hometown = request.args.get('hometown')
university = request.args.get('university')
page = request.args.get('page')
return get_list(**request.args)
I have tried implementing this on my flask application, and it works smoothly.
Of course, one drawback that can be is if there are multiple values like page that are not a part of the model, then each of them has to be defined separately in the get_list, but that can be done by list comprehension
Related
I have two models: Company and Service. In order to filter on Service objects based on the company website, I do the following:
company = Company.objects.get(id = some_id)
service = Service.objects.filter(website = company.website)
Now, suppose I have a function f(website) that only returns the hostname of the website, and I want to filter for instances where f(website of the service) would be equal to f(website of the company). Now, if I do the following:
company = Company.objects.get(id = some_id)
service = Service.objects.filter(f(website) = f(company.website))
it gives syntax errors (obviously).
What is the proper way to do this simple filtering?
In both Service and Company, the website is a normal string and does not have any fields.
Think you are looking for something like this:
service = Service.objects.filter(website__hostname = company.website.hostname)
Where hostname is a field of Website.
In the examples given so far, we have constructed filters that compare the value of a model field with a constant. But what if you want to compare the value of a model field with another field on the same model?
Django provides F expressions to allow such comparisons. Instances of F() act as a reference to a model field within a query. These references can then be used in query filters to compare the values of two different fields on the same model instance.
For example, to find a list of all blog entries that have had more comments than pingbacks, we construct an F() object to reference the pingback count, and use that F() object in the query:
from django.db.models import F
Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Filters can reference fields on the model
I have made a really long form with the help of colander alchemy and deform.
This form has 100 or so fields and currently the only way I know to add the data back to the database once form is submitted is to explicitly re-define each variable and then add that to the database but there must be a better way.
#my schema
class All(colander.MappingSchema):
setup_schema(None,atr)
atrschema =atr.__colanderalchemy__
setup_schema(None,chemicals)
chemicalsschema =chemicals.__colanderalchemy__
setup_schema(None,data_aquisition)
data_aquisitionschema =data_aquisition.__colanderalchemy__
setup_schema(None,depositor)
depositorschema =depositor.__colanderalchemy__
setup_schema(None,dried_film)
dried_filmschema =dried_film.__colanderalchemy__
form = All()
form = deform.Form(form,buttons=('submit',))
# this is how I get it to work by redefining each field but there must be a better way
if 'submit' in request.POST:
prism_material = request.params['prism_material']
angle_of_incidence_degrees =
request.params['angle_of_incidence_degrees']
number_of_reflections = request.params['number_of_reflections']
prism_size_mm = request.params['prism_size_mm']
spectrometer_ID = 6
page = atr (spectrometer_ID=spectrometer_ID,prism_size_mm=prism_size_mm,number_of_reflections=number_of_reflections,angle_of_incidence_degrees=angle_of_incidence_degrees,prism_material=prism_material)
request.dbsession.add(page)
Would like to somehow just be able to remap all of that 'multi dictionary' that is returned back to the database?
So, you have a dict (request.params) and want to pass the key-value pars from that dict to a function? Python has a way to do that using **kwargs syntax:
if 'submit' in request.POST:
page = Page(spectrometer_ID=6,**request.params)
request.dbsession.add(page)
(this works also because SQLAlchemy provides a default constructor which assigns the passed values to the mapped columns, no need to define it manually)
Of course, this is a naive approach which will only work for the simplest use-cases - for example, it may allow passing parameters not defined in your schema which may create a security problem; the field names in your schema must match the field names in your SQLAlchemy model; it may not work with lists (i.e. multiple values with the same name which you can access via request.params.get_all(name)).
As a Flask beginner, I can't understand how request.args is used. I read somewhere that it is used to return values of query string (correct me if I'm wrong) and how many parameters request.args.get() takes.
I know that when I have to store submitted form data, I can use fname = request.form.get("firstname"). Here, only one parameter is passed, whereas the code below takes two parameters.
#app.route("/")
def home():
cnx = db_connect()
cur = cnx.cursor()
output = []
page = request.args.get('page', 1)
try:
page = int(page)
skip = (page-1)*4
except:
abort(404)
stmt_select = "select * from posts limit %s, 4;"
values=[skip]
cur.execute(stmt_select,values)
x=cur.fetchall()
for row in reversed(x):
data = {
"uid":row[0],
"pid":row[1],
"subject":row[2],
"post_content":row[3],
"date":datetime.fromtimestamp(row[4]),
}
output.append(data)
next = page + 1
previous = page-1
if previous<1:
previous=1
return render_template("home.html", persons=output, next=next, previous=previous)
Please explain why it takes two parameters, and then what its use is.
According to the flask.Request.args documents.
flask.Request.args
A MultiDict with the parsed contents of the query string. (The part in the URL after the question mark).
So the args.get() is method get() for MultiDict, whose prototype is as follows:
get(key, default=None, type=None)
In newer version of flask (v1.0.x and v1.1.x), flask.Request.args is an ImmutableMultiDict(an immutable MultiDict), so the prototype and specific method above are still valid.
As a newbie using Flask and Python myself, I think some of the other answers here take for granted that you have a good understanding of the fundamentals. In case you or other readers don't, I'll give more context
... request.args returns a "dictionary" object for you. The "dictionary" object is similar to other collection-type of objects in Python, in that it can store many elements in one single object. Therefore the answer to your question
And how many parameters request.args.get() takes.
It will take only one object, a "dictionary" type of object (as stated in the previous answers). This "dictionary" object, however, can have as many elements as needed... (dictionaries have paired elements called Key, Value).
Other collection-type of objects besides "dictionaries", would be "tuple", and "list"... you can run a google search on those and "data structures" in order to learn other Python fundamentals. This answer is based Python; I don't have an idea if the same applies to other programming languages.
It has some interesting behaviour in some cases that is good to be aware of:
from werkzeug.datastructures import MultiDict
d = MultiDict([("ex1", ""), ("ex2", None)])
d.get("ex1", "alternive")
# returns: ''
d.get("ex2", "alternative")
# returns no visible output of any kind
# It is returning literally None, so if you do:
d.get("ex2", "alternative") is None
# it returns: True
d.get("ex3", "alternative")
# returns: 'alternative'
request.args is a MultiDict with the parsed contents of the query string.
From the documentation of get method:
get(key, default=None, type=None)
Return the default value if the
requested data doesn’t exist. If type is provided and is a callable it
should convert the value, return it or raise a ValueError if that is
not possible.
I have defined optional variables in my django model. In my view, I might have those values or they might be None. I want to create that object without worrying about sending a None argument to the django model.
For example, Book object has a title, but publisher is optional.
right now in my view I'm doing something like
if publisher is None:
Book.objects.create(title=title)
else:
Book.objects.create(title=title, publisher=publisher)
Now this isn't manageable if there are multiple optional fields. What's the solution?
How about using ** operator:
attrs = {'title': title}
if publisher is not None:
attrs['publisher'] = publisher
Book.objects.create(**attrs)
UPDATE alternative - using Model.save:
book = Book(title='title')
if publisher is not None:
book.publisher = publisher
book.save()
Take a look at this:
Call a function with argument list in python
Basically create an array like args and then pass it in as *args to the method required.
You can also do something similar with **kwargs. Take a look at this:
Passing a list of kwargs?
Branching off the other answers... try this
def make_book(**kwargs):
query = {key: value for key, value in kwargs.items() if value}
Book.objects.create(**query)
I suggest declaring this as a method on your models manager so that you can quickly make instances where ever you need them like
Book.objects.make_book(title="The Title",publisher=None)
Sorry for noobster question again.
But I'm trying to do some very easy stuff here, and I don't know how. Documentation gives me hints which do not work, or apply.
I recieve a POST request and grab a variable out of it. It says "name".
I have to search all over my entities Object (for example) and find out if there's one that has the same name. Is there's none, I must create a new Entity with this name. Easy it may look, but I keep Failing.
Would really appreciate any help.
My code currently is this one:
objects_qry = Object.query(Object.name == data["name"])
if (not objects_qry ):
obj = Object()
obj .name = data["name"]
obj .put()
class Object(ndb.Model):
name = ndb.StringProperty()
Using a query to perform this operation is really inefficient.
In addition your code is possibly unreliable, if name doesn't exist and you have two requests at the same time for name you could end up with two records. And you can't tell because your query only returns the first entity with the name property equal to some value.
Because you expect only one entity for name a query is expensive and inefficient.
So you have two choices you can use get_or_insert or just do a get, and if you have now value create a new entity.
Any way here is a couple of code samples using the name as part of the key.
name = data['name']
entity = Object.get_or_insert(name)
or
entity = Object.get_by_id(name)
if not entity:
entity = Object(id=name)
entity.put()
Calling .query just creates a query object, it doesn't execute it, so trying to evaluate is as a boolean is wrong. Query object have methods, fetch and get that, respectively, return a list of matching entities, or just one entity.
So your code could be re-written:
objects_qry = Object.query(Object.name == data["name"])
existing_object = objects_qry.get()
if not existing_object:
obj = Object()
obj.name = data["name"]
obj.put()
That said, Tim's point in the comments about using the ID instead of a property makes sense if you really care about names being unique - the code above wouldn't stop two simultaneous requests from creating entities with the same name.