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.
Related
I'm trying to print the result according to the user's age selection in the form, but my if,elif and else statements are not working.
class Quiz(models.Model):
age_choices = (('10-12', '10-12'),
('13-16', '13-16'),
('17-20', '17-20'),
('21-23','21-23'),
)
age = models.CharField(max_length = 100, choices = age_choices)
views.py
def create_order(request):
form = QuizForm(request.POST or None)
if request.method == 'POST':
quiz = Quiz.objects
if quiz.age=='10-12':
print("10-12")
elif quiz.age=='13-16':
print("13-16")
elif quiz.age=='17-20':
print("17-20")
elif quiz.age=='21-23':
print("21-23")
else:
return None
context = {'form':form}
return render(request, "manualupload.html", context)
quiz = Quiz.objects will return a django.db.models.manager.Manager object and this can be further used to fetch the objects from database belonging to that particular model. The appropriate query set will be quiz = Quiz.objects.all() Then you will get the list of all objects in that belong to Quiz model. Once you get list of all objects, you can get the specific object either by indexing or by filtering using a specific query that you need to look into and then for that particular object you can get the age property.
Refer to official django documentation about creating queries for more information.
As #Abhijeetk431 mentioned, your issue lies in quiz = Quiz.objects.
If you use type(quiz), you will find that it outputs django.db.models.manager.Manager. This is not what you want, as age is a property of the Quiz class, not the Manager class.
For starters, refer to this.
This will return you a Queryset list, something akin to an Excel table. age is akin to the column in the table. To get age, what you want is the row (the actual Quiz object) in said table, which you can achieve using get or using the square brackets [].
Thus, your code should look something like this:
Model.objects.all()[0]
That would return the correct object(only the first row) and allow you to get the column value.
However, further clarification will be needed though, to know exactly what your problem is aside from 'it doesn't work'. How did you know your code is not working; what did the debugger tell you?
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__.
In order to make a simple captacha-like field, I tried the following:
class CaptchaField(IntegerField):
def __init__(self, *args, **kwargs):
super(CaptchaField, self).__init__(*args, **kwargs)
self.reset()
def reset(self):
self.int_1 = random.randint(1, 10)
self.int_2 = random.randint(1, 10)
self.label = '{0} + {1}'.format(self.int_1, self.int_2)
def clean(self, value):
value = super(CaptchaField, self).clean(value)
if value != self.int_1 + self.int_2:
self.reset()
raise ValidationError(_("Enter the result"), code='captcha_fail')
return True
Every time my answer is wrong, the label is changed as expected but the test is performed against the first values of int_1 and int_2 and not against the newly randomly generated values.
I don't understand how Field object are created and why I can't access the values of my field.
Thanks in advance
Have a think about how this works in your view. When you render the form, the field is instantiated and sets the label to your random values, which is fine. Now, the user posts back to the view: what happens? Well, the form is instantiated again, as is the field, and the field is set to two new random values. Not surprisingly, this won't match up to the previous value, because you haven't stored that anywhere.
To do anything like this, you need to store state somewhere so it is preserved between requests. You could try putting it in the session, perhaps: or, a better way might be to hash the two values together and put them in a hidden field, then on submit hash the submitted value and compare it against the one in the hidden field. This would probably need to managed at the form level, not the field.
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.
I have an application where there is a FormWizard with 5 steps, one of them should only appear when some conditions are satisfied.
The form is for a payment wizard on a on-line cart, one of the steps should only show when there are promotions available for piking one, but when there are no promotions i want to skip that step instead of showing an empty list of promotions.
So I want to have 2 possible flows:
step1 - step2 - step3
step1 - step3
The hook method process_step() gives you exactly that opportunity.
After the form is validated you can modify the self.form_list variable, and delete the forms you don't need.
Needles to say if you logic is very complicated, you are better served creating separate views for each step/form, and forgoing the FormWizard altogether.
To make certain forms optional you can introduce conditionals in the list of forms you pass to the FormView in your urls.py:
contact_forms = [ContactForm1, ContactForm2]
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view(contact_forms,
condition_dict={'1': show_message_form_condition}
)),
)
For a full example see the Django docs: https://django-formtools.readthedocs.io/en/latest/wizard.html#conditionally-view-skip-specific-steps
I did it other way, overriding the render_template method. Here my solution. I didn't know about the process_step()...
def render_template(self, request, form, previous_fields, step, context):
if not step == 0:
# A workarround to find the type value!
attr = 'name="0-type" value='
attr_pos = previous_fields.find(attr) + len(attr)
val = previous_fields[attr_pos:attr_pos+4]
type = int(val.split('"')[1])
if step == 2 and (not type == 1 and not type == 2 and not type == 3):
form = self.get_form(step+1)
return super(ProductWizard, self).render_template(request, form, previous_fields, step+1, context)
return super(ProductWizard, self).render_template(request, form, previous_fields, step, context)
There are different ways for this(as mentioned in other answers), but one solution which I think could be useful is overwriting the get_form_list() method:
something like:
from collections import OrderedDict
def get_form_list(self):
form_list = OrderedDict()
// add some condition based on the earlier forms
cleaned_data = self.get_cleaned_data_for_step('step1') or {}
for form_key, form_class in self.form_list.items():
if cleaned_data and cleaned_data['step1'] == 'X':
if form_key == 'step2':
#skip step2
continue
else:
pass
elif cleaned_data and cleaned_data['step1'] == 'Y':
if form_key == 'step4':
#skip step4
continue
else:
pass
....
# try to fetch the value from condition list, by default, the form
# gets passed to the new list.
condition = self.condition_dict.get(form_key, True)
if callable(condition):
# call the value if needed, passes the current instance.
condition = condition(self)
if condition:
form_list[form_key] = form_class
return form_list
I think in this way you can handle complicated forms and you'r not gonna have any conflict with other stuffs.