Django session data obtainable from models.py without key - python

I want to access a session variable from a model, in order to customize what gets shown in a template using {{item.seller_email}}. relevant code from models.py:
SESSION_STORE = import_module(settings.SESSION_ENGINE).SessionStore
class Item(Action):
def seller_email(self):
seller = self.get_seller()
session = SESSION_STORE()
debug('session: %s' % vars(session))
viewer = fetch(Client, email = session.get('client', None))
administrator = viewer.is_admin() if viewer else False
debug('viewer: %s, administrator: %s' % (viewer, administrator))
if administrator:
return seller.email
else:
return seller.anonymized[16:]
the answer here seems to say that this is not possible nor desired, but I lack the understanding of Django's session mechanism to be sure: Access session / request information outside of views in Django
this seems to indicate that the current session can be retrieved outside of views, though, just by not passing a key: https://docs.djangoproject.com/en/1.6/topics/http/sessions/#using-sessions-out-of-views, but I've tried it both that way and the above way with the same results: an "empty" session is created.
I'm sure there are other, better ways of doing this, and I will find one myself, but can someone familiar with Django internals explain why this is not possible?

Because the model shouldn't know anything about session, that creates unnecessary entanglement(?) beetween different components and violation of MVC pattern.
What I suggest to do, is to call the function, specifying parameters needed to get a proper result, viewer object in this case.
If using the code in the question, how do you expect your model to work if it's used in a management command (where there is no http request and as a result no session) ?
Or from a celery job ?

Related

Django Template session not updating values

I am developing a human vs human chess app in django. The part for pawn promotion is not working. The session values are changing,but not getting updated to django template.
The promote view
def promote(request):
#Update board with promoted piece
board = request.session['board']
target = request.session['promote_target']
board[target[0]][target[1]] = request.POST['piece']
request.session['board'] = board
request.session['promotion'] = False
request.session['player'] = 1
return render(request,'chess_app/Default.htm')
The js Function to call server
function promotion(piece){
//Function to promote pawns
//Add a confirm message
$.ajax({url:"{%url 'promote'%}",data:{'piece':piece},type:'post',success:function(){location.reload()}});
}
Everything works fine, but the session is not getting updated
It would be great if you can help.
Check this question I guess it should resolve your problem.
However IMHO use session in template is not very good solution, check this alternative options:
Middleware
You can get values from session and set it in request object in middleware. This option is sensible if you planning to use this values in different views and different templates.
View context
You can put values in view context. This option will be good if you planning to use values only in one view. (And of course you can create a mixin to share this functionality between different view
Inclusion tags
If you can extract part of template that use this data, you can create custom tag for this template and get all required data from request.
Context processor
If you need to share this values between all templates you may use context processors.
I'm not sure why this is not posted in this thread after being asked like a year ago.
The session values are changing, but not getting updated to django template.
To fix this you simply tell Django that sessions have been modified and then it knows to update them in the template:
# make Django update sessions in templates
request.session.modified = True
Here are the docs:
https://docs.djangoproject.com/en/2.2/topics/http/sessions/#when-sessions-are-saved
So to put this in context:
def promote(request):
#Update board with promoted piece
board = request.session['board']
target = request.session['promote_target']
board[target[0]][target[1]] = request.POST['piece']
request.session['board'] = board
request.session['promotion'] = False
request.session['player'] = 1
# make Django update sessions in templates
request.session.modified = True
return render(request,'chess_app/Default.htm') # you are good

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

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.

Django Unittesting adding a variable to the session

In my unittest I need to add a variable to the session, because that variable is used in the view which is being tested. The django documentation says this is possible in the following way (https://docs.djangoproject.com/en/1.3/topics/testing/#django.test.client.Client.session):
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
This code example actually doesn't work, because you will get the error that a dict doesn't have a function save. I also tried various other ways to change the contents inside the session dict, but haven't found a way to change it yet.
I know what it means, what i get back is a dict object so it doesnt have the save function. But the session dict also doesn't update when adding keys.
The documentation statues when using self.client.session it should return a SessionStore object instead of a dictionary.
Seems right now there is a bug in Django, so it doesn't work for unauthenticated users to change the session. This is the corresponding ticket: https://code.djangoproject.com/ticket/11475 .
A work around is to create a dummy view in which the session variables are set and calling that view with the Client.get('url_of_dummy_view').
The ticket referenced by #Sam Stoelinga has been closed as a duplicate. The corresponding ticket can be found here.
To save session data you will have to use an authenticated user. I found a solution here.
class BlogAdminTestCase(TestCase):
def setUp(self):
# Setup Test User
User.objects.create_user(
username='foo',
password='bar'
)
# Must login to modify session variables
self.client.login(username='foo', password='bar')
s = self.client.session
s['my_session_variable'] = 'Yay!'
s.save()

KindError in Google App Engine

I defined a simple class in GAE for keeping user profiles data like this:
class User(db.Model):
email = db.EmailProperty()
role = db.StringProperty(default=roles.USER)
first_name = db.StringProperty()
last_name = db.StringProperty()
...
I use memcache to keep session information. memcache data looks like this { 'key': 'agpjYW5kaXJhdGVzcgoLEgRVc2VyGCMM'}. I get session_id value from the cookie. When I try to get user info linked to that cookie like this:
session_id = request['session_id']
data = memcache.get(session_id)
user = User.get(data['key'])
I get KindError exception:
KindError: Kind 'User' is not a subclass of kind 'User'
I know this user exists, memcache exists. User class is defined only once in my project. Why this error occurs and how can I make it work?
UPDATE: I tried to use db.get() instead of User.get() and it worked. So, what's the problem there can be?
Model.get() does check whether the supplied key is of the correct kind, as defined in the documentation. If not of the correct kind it will throw a KindError.
db.get() does not do any type checking and therefore will succeed with the supplied value if it exists in the data store, but will not necessarily return a User entity.
So you need to check whether the key in your memcache is actually of the User kind. Are you sure it's not overwritten with the key of a different model at some point?
The App Engine framework defines a class called 'User' as part of the Users API. In addition, you have your own class by the same name. When the exception occurs, you're trying to use one, but getting the other.
To avoid this, rename your model. You should also be careful how you import modules in Python. Instead of:
from google.appengine.api.users import User
or worse:
from google.appengine.api.users import *
you should use:
from google.appengine.api import users
And then refer to users.User, which is unambiguous.
The problem, it seems to me, is more subtle than that. I was getting the error with this call to Model.get() (I'm retrieving a top-level singleton object, always there):
datastore = GDSDatastore.get(gds.Key.from_path(*path))
so I investigated with this code:
datastore = gds.get(gds.Key.from_path(*path))
if not(datastore is None or isinstance(datastore, GDSDatastore)):
logger.error("KindError isinstance(GDSDatastore)=%s class=%s" % (isinstance(datastore, GDSDatastore), datastore.__class__.__name__))
raise gds.KindError('Kind %r is not a GDSDatastore instance' %
(datastore.kind()))
The vast majority of the time I get no error, but today I got this interesting log:
KindError isinstance(GDSDatastore)=False class=GDSDatastore
Now, that strikes me as rather peculiar.
(Note: GDSDatastore is defined locally: class GDSDatastore(gds.Model))

Django: Obtaining the absolute URL without access to a request object

I have a model like the one below. When an instance is created, I want to send out an e-mail to an interested party:
class TrainStop(models.Model):
name = models.CharField(max_length=32)
notify_email = models.EmailField(null=True, blank=True)
def new_stop_created(sender, instance, created, *args, **kwargs):
# Only for new stops
if not created or instance.id is None: return
# Send the status link
if instance.notify_email:
send_mail(
subject='Stop submitted: %s' % instance.name,
message='Check status: %s' % reverse('stop_status', kwargs={'status_id':str(instance.id),}),
from_email='admin#example.com',
recipient_list=[instance.notify_email,]
)
signals.post_save.connect(new_stop_created, sender=TrainStop)
However, the reverse call only returns the path portion of the URL. Example: /stops/9/status/. I need a complete URL like http://example.com/stops/9/status/. How would I go about retrieving the hostname and port (for test instances that do not use port 80) of the current website?
My initial thought was to make this available via a variable in settings.py that I could then access as needed. However, thought someone might have a more robust suggestion.
There's the sites framework, as yedpodtrzitko mentioned, but, as you mentioned, it's very much a manual setup.
There's requiring a setting in settings.py, but it's only slightly less manual than setting up sites. (It can handle multiple domains, just as well as sites and the SITE_ID setting can).
There's an idea for replacing get_absolute_url, that would make stuff like this easier, though I think its implementation suffers from the same problem (how to get the domain, scheme [http vs https], etc).
I've been toying with the idea of a middleware that examines incoming requests and constructs a "most likely domain" setting of some sort based on the frequency of the HTTP HOST header's value. Or perhaps it could set this setting on each request individually, so you could always have the current domain to work with. I haven't gotten to the point of seriously looking into it, but it's a thought.
For getting current site there's object Site:
If you don’t have access to the request object, you can use the
get_current() method of the Site model’s manager. You should then
ensure that your settings file does contain the SITE_ID setting. This
example is equivalent to the previous one:
from django.contrib.sites.models import Site
def my_function_without_request():
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
# Do something
pass
else:
# Do something else.
pass
More info: http://docs.djangoproject.com/en/dev/ref/contrib/sites/

Categories

Resources