Passing in Session data from Flask to WTForms - python

So I'm having issues with passing in a list from my Flask session object into the WTForms that I'm using.
I'm trying to access the 'strategy' object (it's a list) within the session object.
The goal is to list all of the strategies that are associated with the current user.
Within the Flask app:
class Create_Indicator_Form(Form):
security = TextField('Ticker Name', [
validators.Required(),
validators.Length(min=1, max=6)])
mva_10 = BooleanField('10 day moving average')
mva_25 = BooleanField('25 day moving average')
strategy = SelectField('strategy', session['strategy'])
def validate_mva_10(form, field):
if form.mva_25.data is True and field.data is True:
raise ValidationError('You can only choose one reference')
if form.mva_25.data is False and field.data is False:
raise ValidationError('You must choose at least one reference')
#app.route('/create_indicator', methods=['GET', 'POST'])
def create_indicator():
check_if_logged_in()
f = request.form
create_indicator_form = Create_Indicator_Form(f)
if request.method == 'POST' and create_indicator_form.validate():
indicator_id = get_next_index('indicator', 'indicator_id')
ticker = create_indicator_form.security.data
if create_indicator_form.mva_10.data is True:
mva_10_day = 'Y'
mva_25_day = 'N'
else:
mva_10_day = 'N'
mva_25_day = 'Y'
row = [indicator_id, ticker, mva_10_day, mva_25_day]
add_data('indicator', row)
# adding relation
criteria_row = [session['strategy'][0], indicator_id]
add_data('criteria', criteria_row)
return redirect(url_for('home'))
create_indicator_form.strategies = session['strategy']
return render_template('create_indicator.html',
form=create_indicator_form)
When I try to run the flask app I'm thrown this error:
RuntimeError: working outside of request context
with a trace back to where I access the session object in the Create_Indicator_Form class.
I've realized since starting to try to fix this that it'd be more efficient to chose between the mva_10 and mva_25 with a select field but I would like to resolve this issue before refactoring.

Why
This is because the strategy = SelectField('strategy', session['strategy']) line is executed when the file is loaded, as it is part of the Create_Indicator_Form class definition.
At this time, you're indeed working outside of request context
How to fix it
Using a class factory will work here:
def CreateIndicatorForm():
class IndicatorForm(Form):
security = TextField('Ticker Name', [
validators.Required(),
validators.Length(min=1, max=6)])
mva_10 = BooleanField('10 day moving average')
mva_25 = BooleanField('25 day moving average')
strategy = SelectField('strategy', session['strategy'])
def validate_mva_10(form, field):
if form.mva_25.data is True and field.data is True:
raise ValidationError('You can only choose one reference')
if form.mva_25.data is False and field.data is False:
raise ValidationError('You must choose at least one reference')
return IndicatorForm
Be mindful that calling CreateIndicatorForm() returns a Form class, so to instantiate an actual form, you need to use: CreateIndicatorForm()() (i.e. "call" the class, like you would any other class to create a new instance).
When instantiating the form, you can (and should) pass arguments to the form constructor, such as the request data: CreateIndicatorForm()(request.form).
Also, making session['strategy'] an argument of the factory function would be better practice here - this would make the factory reusable outside of request context and would ensure you don't have to create a new form class for every single request.

Related

OTree / Django send player to results/ exit page if StringField input not in my dict

I’m working with OTree with self: https://otree.readthedocs.io/en/self/
On the first page of my experiment, I ask the players to provide an access code / their matriculation number via StringField. If their input is not in my dict in Constants, I want to send them directly to a page where I tell them „Sorry, you can’t participate“ with the only option for them to click the next button and exit the experiment.
I have tried the following:
in models.py
class Constants(BaseConstants):
name_in_url = 'prisoners_test1'
players_per_group = 2
num_rounds = 1
matriculation_dict = {
'123': ('Adam Smith', 'Economics'),
'124': ('Ada Lovelace', 'Programming'),
'125': ('Charles Babbage', 'Mathematics'),
}
class Player(BasePlayer):
matriculation = models.StringField(label='Please provide your Matriculation Number')
access = models.BooleanField()
def matriculation_not_found(self):
if self.matriculation in Constants.matriculation_dict:
self.access = True
else: self.access = False
in pages.py
class ExcludedPlayer(Page):
def is_displayed(self):
return self.player.access == False
page_sequence = [Matriculation, ExcludedPlayer, P1_Decision, P2_Decision, ResultsWaitPage, Results]
The problem is that the value of access is not updated through my if statement.
My second problem is that even if the page ExcludedPlayer is displayed (b/c I set initial value of access = False), the player is directed to the other pages (P1_Decision, ResultsWaitPage, Results) after clicking next. How can I end the game for the excluded player?
Thank you for your help!
To your first problem:
To update the access field, you need to call your matriculation_not_found method somewhere. A good place for this is the otree built-in method before_next_page in your Matriculation class:
class Matriculation(Page):
def before_next_page(self):
self.player.matriculation_not_found()
Or in newer otree versions (no-self format):
class Matriculation(Page):
#staticmethod
def before_next_page(player, timeout_happened):
player.matriculation_not_found()
To your second problem:
The easiest way to prevent the excluded players from seeing the upcoming pages is to remove the next-button. Simply delete the following line from the ExcludedPlayer.html template:
{{ next_button }}
If for some reason you don't want that, you can also check on each of the upcoming pages in the is_displayed method whether access is allowed or not. For example for the P1_Decision page:
class P1_Decision(Page):
def is_displayed(self):
return self.player.access
And again the same in the new no-self format:
class P1_Decision(Page):
#staticmethod
def is_displayed(player):
return player.access
Another alternative would be to swap out the ExcludedPlayers page to a later app (let's call it 'exculuded_players_app') and skip the pages (and apps) in between using the app_after_this_page method:
class Matriculation(Page):
def before_next_page(self):
self.player.matriculation_not_found()
def app_after_this_page(self, upcoming_apps):
if not self.player.access:
return 'exculuded_players_app'
And again the same in the new no-self format:
class Matriculation(Page):
#staticmethod
def before_next_page(player, timeout_happened):
player.matriculation_not_found()
#staticmethod
def app_after_this_page(player, upcoming_apps):
if not player.access:
return 'exculuded_players_app'

Does transaction.atomic cover the called function too?

I'm working with Django / Celery and I would like to know if transaction.atomic on my create function cover also the called function (createUserTenant) too
this is an example (as you can see i'm calling createUserTenant which contains some queries) :
#transaction.atomic
def create(self, request):
formSerializer = CustomUserSerializer(data = request.data)
if formSerializer.is_valid():
NewUserRecord = formSerializer.save()
if createUserTenant.delay(NewUserRecord.id, connection.schema_name):
return Response(TeamSerializer(Team.objects.all(), many=True).data, status = status.HTTP_201_CREATED)
return Response(formSerializer.errors, status.HTTP_400_BAD_REQUEST)
As you can see I have some transactions here
#shared_task
def createUserTenant(userid, current_schema):
state = True
try:
with schema_context(current_schema):
addUserInTeam = Team.objects.create(user = CustomUser.objects.get(pk=userid))
with schema_context('public'):
userInQuestion = CustomUser.objects.get(id=userid)
# create your first real tenant
tenant = Client(
schema_name=str(userInQuestion.username),
name=userInQuestion.first_name + '_' + userInQuestion.last_name,
paid_until='2014-12-05',
on_trial=True)
tenant.save()
# migrate_schemas automatically called, your tenant is ready to be used!
# Add one or more domains for the tenant
domain = Domain()
domain.domain = str(userInQuestion.username) + settings.BASE_URL # tx.domain.com
domain.tenant = tenant
domain.is_primary = False
domain.save()
except:
state = False
return state
No, the decorator only wraps the function create in a transaction block and transactions can also be nested. A transaction block does not automatically wraps the parent block in a transaction. You need to create a transaction in your task to ensure that the code outside the create is being run in a transaction.
Here are more details: https://docs.djangoproject.com/en/3.2/topics/db/transactions/#controlling-transactions-explicitly
One way to do it:
from django.db import transaction
#shared_task
def createUserTenant(userid, current_schema):
with transaction.atomic():
# your code
# ....

Django: Value being set to None after Object.get

Ok so this question stems from another one that I posted (Old Post). Essentially I have a view that is trying to assign a value to a ForeignKey of a newly created object new_protocol. The problem is that for some reason that value is being set to none.
What I don't understand is that at the beginning of the view I call the get_object_or_404method so there is not reason for that to be set to none. Any thoughts would be appreciated.
The view.py
def add_protocol(request, study_slug):
study = get_object_or_404(Study, slug=study_slug)
if request.method == 'POST':
new_protocol = AddProtocol(request.POST, request.FILES)
if new_protocol.is_valid():
new_protocol.save(commit=False)
new_protocol.study = study
new_protocol.save()
return HttpResponseRedirect(reverse('studies:studydetails', args=(study.slug,)))
else:
return HttpResponse('Something is messed up')
else:
new_protocol = AddProtocol()
return render(request, 'studies/addprotocol.html', {'new_protocol': new_protocol, 'study': study})
If AddProtocol is the ModelForm (wouldn't it be better to name it AddProtocolForm?), then
# ...
# I renamed new_protocol to new_protocol_form here
new_protocol_form = AddProtocol(request.POST, request.FILES)
if new_protocol_form.is_valid():
# save() method of form returns instance
new_protocol = new_protocol_form.save(commit=False)
# assigning related field
new_protocol.study = study
new_protocol.save()
# ...
save() method
In your code you assigned study to form (not model), so model's study got value None.

Why is this class and corresponding attribute not being destroyed between requests?

I can not understand this behavior at all. I asked a question yesterday or the day before thinking it was something with bottle.py, but after trying all kinds of possible solutions, even converting my app over to flask, I have pinpointed the behavior to a single, very simple, class, but I have NO idea why this is happening. It's confusing the hell out of me, but I would really like to understand this if anyone can please shed some light on it.
Ok, so first I have a class called Page which simplifies setting up templates a bit, this is the offending class:
class Page:
"""The page object constructs the webpage and holds associated variables and templates"""
def __init__(self, template=None, name = '', title='',template_vars={}, stylesheets=[], javascript=[]):
# Accepts template with or without .html extension, but must be added for lookup
self.stylesheet_dir = '/css'
self.javascript_dir = '/js'
self.template = template
self.template_vars = {}
self.name = name
self.title = title
self.stylesheets = stylesheets
self.javascript = javascript
self.template_vars['debug'] = _Config.debug
self.template_vars['title'] = self.title
self.template_vars['stylesheets'] = self.stylesheets
self.template_vars['javascript'] = self.javascript
def render(self):
"""Should be called after the page has been constructed to render the template"""
if not self.template.endswith(_Config.template_extension):
self.template += '.' + _Config.template_extension
if not self.title:
if self.name:
self.title = _Config.website + _Config.title_delimiter + self.name
else:
# If title not given use template name
self.title = _Config.website + _Config.title_delimiter + self.template.rstrip('.html')
try:
template = env.get_template(self.template)
except AttributeError:
raise (AttributeError, 'template not set')
rendered_page = template.render(self.template_vars)
return rendered_page
def add_stylesheet(self, name, directory=None):
# Sanitize name
if not name.endswith('.css'):
name += '.css'
if name.startswith('/'):
name = name.lstrip('/')
if not directory:
directory = self.stylesheet_dir
self.template_vars['stylesheets'].append(directory + '/' + name)
def add_javascript(self, name, directory=None):
# Sanitize name
if not name.endswith('.js'):
name += '.js'
if name.startswith('/'):
name = name.lstrip('/')
if not directory:
directory = self.javascript_dir
self.template_vars['javascript'].append(directory + '/' + name)
And here is an example of a route that the problem is exhibited:
#route('/create_account', method=['GET','POST'])
def create_account():
dbsession = db.Session()
page = Page('create account')
page.add_javascript('create_account.js')
if request.method == 'GET':
page.template = 'create_account'
page.template_vars['status'] = None
document = page.render()
dbsession.close()
return document
elif request.method == 'POST':
# Omitted
The problem lies with the method Page.add_javascript(). The next time I go to the /create_account page it is not creating a new Page object and instead reusing the old one. I know this because if I go to the page twice I will have two entires for the create_account.js in my returned html document(the template simply runs a for loop and creates a tag for any js files passed in that list). If I go 3 times it'll be listed 3 times, 40, 40 and so on. Now however if I simply change it to use the initializer and not the add_javascript method the problem goes away.
#route('/create_account', method=['GET','POST'])
def create_account():
dbsession = db.Session()
page = Page('create account', javascript=['create_account.js'])
if request.method == 'GET':
page.template = 'create_account'
page.template_vars['status'] = None
document = page.render()
dbsession.close()
return document
elif request.method == 'POST':
However I suspect something is still wrong and just for my own sanity I need to understand what the hell is going on here. What is possibly happeneing behind the scenes where the add_javascript method would be called twice on the same page object? The method is called immediately after creating a new instance of the page object, where is it possibly getting the old contents of template_vars from?
The problem is that you use mutable defaults for your Page.__init__ function. See http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments.
So you do get a new Page instance on each request, but the lists/dictionaries that hold your javascript etc are re-used.
Replace list/dict default argument values with None, check for None in __init__.

Tracking model changes in SQLAlchemy

I want to log every action what will be done with some SQLAlchemy-Models.
So, I have a after_insert, after_delete and before_update hooks, where I will save previous and current representation of model,
def keep_logs(cls):
#event.listens_for(cls, 'after_delete')
def after_delete_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'after_insert')
def after_insert_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'before_update')
def before_update_trigger(mapper, connection, target):
prev = cls.query.filter_by(id=target.id).one()
# comparing previous and current model
MODELS_TO_LOGGING = (
User,
)
for cls in MODELS_TO_LOGGING:
keep_logs(cls)
But there is one problem: when I'm trying to find model in before_update hook, SQLA returns modified (dirty) version.
How can I get previous version of model before updating it?
Is there a different way to keep model changes?
Thanks!
SQLAlchemy tracks the changes to each attribute. You don't need to (and shouldn't) query the instance again in the event. Additionally, the event is triggered for any instance that has been modified, even if that modification will not change any data. Loop over each column, checking if it has been modified, and store any new values.
#event.listens_for(cls, 'before_update')
def before_update(mapper, connection, target):
state = db.inspect(target)
changes = {}
for attr in state.attrs:
hist = attr.load_history()
if not hist.has_changes():
continue
# hist.deleted holds old value
# hist.added holds new value
changes[attr.key] = hist.added
# now changes map keys to new values
I had a similar problem but wanted to be able to keep track of the deltas as changes are made to sqlalchemy models instead of just the new values. I wrote this slight extension to davidism's answer to do that along with slightly better handling of before and after, since they are lists sometimes or empty tuples other times:
from sqlalchemy import inspect
def get_model_changes(model):
"""
Return a dictionary containing changes made to the model since it was
fetched from the database.
The dictionary is of the form {'property_name': [old_value, new_value]}
Example:
user = get_user_by_id(420)
>>> '<User id=402 email="business_email#gmail.com">'
get_model_changes(user)
>>> {}
user.email = 'new_email#who-dis.biz'
get_model_changes(user)
>>> {'email': ['business_email#gmail.com', 'new_email#who-dis.biz']}
"""
state = inspect(model)
changes = {}
for attr in state.attrs:
hist = state.get_history(attr.key, True)
if not hist.has_changes():
continue
old_value = hist.deleted[0] if hist.deleted else None
new_value = hist.added[0] if hist.added else None
changes[attr.key] = [old_value, new_value]
return changes
def has_model_changed(model):
"""
Return True if there are any unsaved changes on the model.
"""
return bool(get_model_changes(model))
If an attribute is expired (which sessions do by default on commit) the old value is not available unless it was loaded before being changed. You can see this with the inspection.
state = inspect(entity)
session.commit()
state.attrs.my_attribute.history # History(added=None, unchanged=None, deleted=None)
# Load history manually
state.attrs.my_attribute.load_history()
state.attrs.my_attribute.history # History(added=(), unchanged=['my_value'], deleted=())
In order for attributes to stay loaded you can not expire entities by settings expire_on_commit to False on the session.

Categories