Flask/WTF/SQLAlchemy: using QuerySelectMultipleField with Form.populate_obj() - python

I have a form (WTForms via Flask-WTF) that includes a QuerySelectMultipleField, something like this:
class EditDocumentForm(Form):
# other fields omitted for brevity
users = QuerySelectMultipleField('Select Users',
query_factory=User.query.all,
get_label=lambda u: u.username)
This works great—I just instantiate the form and pass it to my template for rendering, and all the right choices are there.
However, when I POST the form back and try to suck up the data with Form.populate_obj(), I get an angry message from SQLAlchemy:
InvalidRequestError: Object '<User at 0x10a4d33d0>' is already attached to session '1' (this is '3')
The view function:
#app.route("/document/edit/<doc_id>", methods=['GET', 'POST'])
#login_required
def edit_document(doc_id):
doc = Document.query.filter_by(id=doc_id).first()
if (doc is not None) and (doc.user_id == current_user.id):
form = EditDocumentForm(obj=doc)
if request.method == "POST":
if form.validate():
form.populate_obj(doc)
db.session.commit()
return redirect('/')
else:
_flash_validation_errors(form)
return render_template("edit.html", form=form)
flash("The document you requested doesn't exist, or you don't have permission to access it.", "error")
return(redirect('/'))
So it looks like there's one session used when the form is created, and another when I'm trying to populate my model object. This is all happening under the hood, as I'm relying on Flask-SQLAlchemy to do all the session stuff for me.
In the Document model, the user field is declared this way:
users = db.relationship('User',
secondary=shares,
backref=db.backref('shared_docs', lazy='dynamic'))
(where of course shares is an instance of SQLAlchemy.table for a many-to-many relationship).
So: am I doing something wrong, or is Form.populate_obj() the problem, or perhaps I can blame aliens? Let me rephrase: What am I doing wrong?
Edit
The workaround this answer seems to fix the problem, namely changing my query_factory by importing my SQLAlchemy object and explicitly using its session:
query_factory=lambda: db.session.query(User)
I have to say, though, this has a weird smell to me. Is this really the best way to handle it?

It all depends on how your models classes are bound to a session. My guess is: you're not using the base class provided by Flask-SQLAlchemy: db.Model for your Document and User models ?
As stated in your ''edit'', by not using User's query method, and using db.session.query instead, you are forcing populate_obj to use the same session that you will commit later with your db.session.commit call. That said, you are still probably using another session when doing Document.query.filter_by which most likely means you are still using 2 DB connections and could reduce it to one.
Overall, I would advise you to stay away from using the query method on your models (but that's because I don't like magic stuff ;) ), make sure to use Flask-SQLAlchemy's db.Model if you can and read in-depth how the framework / libraries you use work, as it's a very good habit to take, does not take a lot of time, and can significantly improve the quality and maintainability of your code.

Related

Flask / SQL Alchemy raw query instead of class method

I'll acknowledge that this is a strange question before I ask it. I'm wondering if it's possible to replicate Flask / SQL Alchemy class methods using raw SQL instead of using the methods themselves?
Long story short, my teammates and I are taking a database design course, and we're now in the implementation phase where we are coding the app that is based on our DB schema design. We want to keep things simple, so we opted for using Flask in Python. We're following the Flask Mega Tutorial, which is a kickass-tic tutorial explaining how to build a basic site like we're doing. We've just completed Chapter 5: User Logins, and are moving on.
In the app/routes.py script, the tutorial does something to grab the user information. Here's the example login route for the example app:
from flask_login import current_user, login_user
from app.models import User
# ...
#app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title='Sign In', form=form)
The line user = User.query.filter_by(username=form.username.data).first() is what I'm interested in. Basically, that line instantiates the User class, which is a database model from SQL Alchemy, and grabs information about the user from the email address they entered. Calling those methods generates a SQL statement like the following:
SELECT `User`.`userID` AS `User_userID`,
`User`.user_email AS `User_user_email`,
`User`.user_first_name AS `User_user_first_name`,
`User`.user_last_name AS `User_user_last_name`,
`User`.user_password AS `User_user_password`
FROM `User`
WHERE `User`.user_email = 'test#test.com'
LIMIT 1
And also some information about the user variable itself:
>>> print(type(user))
<class 'myapp.models.User'>
>>> pp(user.__dict__)
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7f5a026a8438>,
'userID': 1,
'user_email': 'test#test.com',
'user_first_name': 'SomeFirstName',
'user_last_name': 'SomeLastName',
'user_password': 'somepassword'}
On our project, we're not supposed to be using generated SQL statements like the one that comes from calling query.filter_by(username=form.username.data).first() on the instantiated User class; we should be writing the raw SQL ourselves, which normally doesn't make sense, but in our case it does.
Is this possible?
First of all: Talk to your professor or TA. You will save yourself time by not making assumptions about something so major. If the goal of the class is to think about database schema design then using an ORM is probably fine. If you need to write your own SQL, then don't use an ORM to begin with.
To answer the technical question: yes, you can use SQLAlchemy purely as a database connection pool, as a tool to create valid SQL statements from Python objects, and as a full-fledged ORM, and every gradation in between.
For example, using the ORM layer, you can tell a Query object to not generate the SQL for you but instead take text. This is covered in the SQLAlchemy ORM tutorial under the Using Textual SQL section:
Literal strings can be used flexibly with Query, by specifying their use with the text() construct, which is accepted by most applicable methods
For your login example, querying for just the password could look like this:
user = User.query.from_statement(
db.text("SELECT * FROM User where user_email=:email LIMIT 1")
).params(email=form.username.data).first()
if user is None or user.check_password(form.password.data):
# ...
You could also read up on the SQL Expression API (the core API of the SQLAlchemy library) to build queries using Python code; the relationship between Python objects and resulting query is pretty much one on one; you generally would first produce a model of your tables and then build your SQL from there, or you can use literals:
s = select([
literal_column("User.password", String)
]).where(
literal_column("User.user_email") == form.username.data
).select_from(table("User")).limit(1)
and execute such objects with the Session.execute() method
results = db.session.execute(s)
If you wanted to really shoot yourself in the foot, you can pass strings to db.session.execute() directly too:
results = db.session.execute("""
SELECT user_password FROM User where user_email=:email LIMIT 1
""", {'email': form.username.data})
Just know that Session.execute() returns a ResultProxy() instance, not ORM instances.
Also, know that Flask-Login doesn't require you to use an ORM. As the project documentation states:
However, it does not:
Impose a particular database or other storage method on you. You are entirely in charge of how the user is loaded.
So you could just create a subclass of UserMixin that you instantiate each time you queried the database, manually.
class User(flask_login.UserMixin):
def __init__(self, id): # add more attributes as needed
self.id = id
#login_manager.user_loader
def load_user(user_id):
# perhaps query the database to confirm the user id exists and
# load more info, but all you basically need is:
return User(user_id)
# on login, use
user = User(id_of_user_just_logged_in)
login_user(user)
That's it. The extension wants to see instances that implement 4 basic methods, and the UserMixin class provides all of those and you only need to provide the id attribute. How you validate user ids and handle login is up to you.

Setting attributes in flask login current user for later access

I am trying to figure out a way to store recommendations for each user on a website so that when a user logs in the recommendations can be collected once and don't need to be updated until the user rates something new.
I thought a nice way to do this would be to store the recommendations in the User class, but it appears that when I try to access this attribute later the values are no longer there. To compound this issue, I found that from view to view the current_user address changes.
Example User class:
class User(UserMixin):
def __init__(self, user_doc):
self.username = user_doc['Username']
self.userid = user_doc['User Id']
self.recommendations = []
def get_recs(self):
self.recommendations = app.config['MODEL'].predict_all(self.userid)
Example views.py:
#APP.route('/', methods=['GET'])
def index():
"""Render the homepage."""
if current_user.is_authenticated:
if current_user.recommendations == []:
current_user.get_recs()
return render_template('index.html')
#APP.route('/recommend', methods=['POST', 'GET'])
#login_required
def recommend():
recs = get_recommendations(current_user)
return render_template('recommend.html', recs=recs[:10])
If I login and go through the homepage, current_user.recommendations get filled as expected. However, if I then navigate to /recommend and place a breakpoint before recs = get_recommendations(current_user) I find that current_user.recommendations is again an empty list. Is this the expected behavior of current_user and if so what is the proper way to store user attributes to prevent repeated calculation.
Thanks in advance for any help.
Edit
Apparently I didn't explain my problem thoroughly enough and it seemed similar to How to user g user global in flask. However, I read that question and it is not in fact in any way similar.
The problem:
My model is moderately complex and requires > 0.1 s to predict all of the recommendations for a user. It seems to me that structuring the website in a way that each time a user navigates to /recommend the recommendations must be calculated is wasteful and could given enough requests, slow down the server.
The solution:
In order to circumvent this problem I thought that calculating the recommendations once upon login and subsequently after new ratings are added would reduce the volume of predictions the server would need to calculate, thus improving performance of the server and the time necessary for loading the recommendations page for each user.
However, I can't seem to set an attribute in the existing current_user object in one view that is then available in another view.
The question:
Is there a way to set attribute values for the current_user object within a view, such that they are accessible to other views?
It's common to use a property for it:
class User(UserMixin):
def __init__(self, user_doc):
self.username = user_doc['Username']
#property
def recommendations(self):
return do_some_stuff()
Then current_user.recommendations would return the results of the do_some_stuff() logic.
It seems to me that current_user is being reloaded every time based on id initially provided.
Without resolving problem in originally desired way, I (as python newbie) suggest you do the trick:
from flask import session
session['data_variable_name'] = 'somedata'
And access session from places you need.

How to specify label_attr for a model in a Flask-Admin ModelView using MongoEngine?

I think I have a pretty common use case and am surprised at how much trouble it's giving me.
I want to use a key-value pair for a ReferenceField in the Flask-Admin edit form generated by the following two classes:
class Communique(db.Document):
users = db.ListField(db.ReferenceField(User), default=[])
class User(db.Document):
email = db.StringField(max_length=255, required=True)
def __unicode__(self):
return '%s' % self.id
I want the select to be constructed out of the ObjectId and the an email field in my model.
By mapping the __unicode__
attribute to the id field I get nice things on the mongoengine side like using the entire object in queries:
UserInformation.objects(user=current_user)
This has the unfortunate effect of causing the Flask-Admin form to display the mongo ObjectId in the edit form like so:
The docs say I have to provide the label_attr to the ModelSelectMultipleField created by Flask-Admin. I've done so by overriding the get_form method on my ModelView:
def get_form(self):
form = super(ModelView, self).get_form()
form.users = ModelSelectMultipleField(model=User,
label_attr='email',
widget=form.users.__dict__['kwargs']['widget'])
return form
I'm reusing the the widget used by the original form.users (which may be wrong). It works fine when editing an existing item, BUT throws an exception when creating a new one (perhaps because I'm reusing the widget).
All of this seems like way more work than should be needed to simply provide a label_attr to my SelectField. Fixing up the listing view was a simple matter of adding an entry to the column_formatters dictionary. Is there no simple way to specify the label_attr when creating my ModelView class?
I know I could make this problem go away by returning the email property in the __unicode__ attribute, but I feel like I shouldn't have to do that! Am I missing something?
Oy, now I see how to do it, though it's not that obvious from the docs. form_args is a dictionary with items keyed to the form models. All I needed to do was...
form_args = dict(users=dict(label_attr='email'))
Which does seem about the right amount of effort (considering Flask-Admin isn't some sort of java framework).

updating user profile fields django

Should be a simple answer but I can't figure out what's wrong here...
I have a user profile with a couple of simple fields. I'm trying to update them like so:
if data['dob'] != None:
request.user.profile.dob = data['dob']
request.user.profile.save()
This doesn't seem to have any effect at all though.
p.s. i am using a nice little trick in my UserProfile class that looks like this:
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
Could this be part of the problem?
Think about what happens in your code.
If there's a dob in your data, you call request.user.profile. This calls your property, which makes a request to the database and gets or creates a Profile instance.
Next, you call request.user.profile again. Guess what this does? Makes a fresh call to the database, and gets an instance of the Profile again. But of course this is a new instance, even though it's referring to the same database row, so it won't have the value for dob you just set on the last version.
Now, potentially you could solve this by storing the profile in a local variable:
profile = request.user.profile
profile.dob = data['dob']
profile.save()
But to be honest, I'd drop the whole hacking around with the profile property. It's going to cause you all sorts of problems.
It might be easier to use the suggested method of tying a profile to a django user:
https://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users
In the meantime, remove the [0] at the end of the UserProfile.objects.get_or_create(user=u) as that method only returns a single object regardless

Setting object owner with generic create_object view in django

Is it possible to use create_object view to create a new object and automatically assign request.user as foreign key?
P.E:
class Post(models.Model):
text = models.TextField()
author = models.ForeignKey(User)
What I want is to use create_object and fill author with request.user.
In many ways, all the solutions to this will be more trouble than they are worth. This one qualifies as a hack. It is possible for a django update to leave you high and dry if they change the way create_update is implemented. For simplicity sake, I'll assume that you are trying to set a default user, not silently force the user to be the logged in user.
Write a context processor:
from django.views.generic.create_update import get_model_and_form_class
def form_user_default(request):
if request.method == 'GET':
model, custom_form = get_model_and_form_class(Post,None)
custom_form.author = request.user
return {'form':custom_form}
else: return {}
What this will do is override the form object that create_update passes to the template. What it's technically doing is re-creating the form after the default view has done it.
Then in your url conf:
url(r'pattern_to_match', 'django.views.generic.create_update.create_object', kwargs={'context_processors':form_user_default})
Again, I had to delve into the source code to figure out how to do this. It might really be best to try writing your own view (but incorporate as many Django custom objects as possible). There's no "simple default" way to do this, because in the django paradigm forms are more closely tied to the model layer than to views, and only views have knowledge of the request object.
You may want to consider a closure.
from django.forms import ModelForm
from django.views.generic.create_update import create_object, update_object
def make_foo_form(request):
class FooForm(ModelForm):
class Meta:
model = Foo
fields = ['foo', 'bar']
def save(self, commit=True):
f = super(FooForm, self).save(commit=False)
if not f.pk: f.user = request.user
if commit: f.save()
return f
return FooForm
def create_foo(request):
FooForm = make_foo_form(request)
return create_object(form_class=FooForm)
There is some inefficiency here, since you need to create the ModelForm object on each request, but it does allow you to inject functionality into the generic view.
You need to decide whether the added complexity for the form creation is worth maintaining simplicity on the view side.
A benefit here, though, is that this also works with the update case with practically no extra effort:
def update_foo(request, object_id):
FooForm = make_foo_form(request)
return update_object(form_class=FooForm, object_id=object_id)
Obviously, you can use this approach for more complex cases as well.
If a user is authenticated, their user object is the request.user object.
I'm not familiar with create_object... I'm still a beginner to django and have only just started my first real project with it.
Note that you should check to make sure a user is logged in before using this. This can be done with request.user.is_authenticated().
There is no good way to hook into the saving of an object when using the current Django generic views. Once they are rewritten as classes, you'll be able to subclass the view and hook in at the proper place without having to rewrite the whole view.
I already use my own class-based generic views for this reason.
I would suggest to make a wrapper for the create_object, as this author suggest
http://www.b-list.org/weblog/2006/nov/16/django-tips-get-most-out-generic-views/
in the view you'll have access to the user info.
Afterwards, you will need to use the extra_context to pass the user to the template. Finally at the template you can add a hidden field with the user info. I haven't tried it, but I have been thinking of it for quite some time. Hope this solution suits you!
;) cheers!

Categories

Resources