Skip steps on a django FormWizard - python

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.

Related

Django if elif else statement

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?

Wagtail/Django: Query filter to return only the pages the user has acess/permissions?

I'm going through the documentation at: http://docs.wagtail.io/en/v2.7.1/reference/pages/queryset_reference.html.
Is there a filter to return only the pages the user has access to? I can only see public() and not_public().
I have some pages which privacy is set to Private (accessible to users in specific groups). And would like to exclude them from the query results.
There is no such filter in PageQuerySet. You can however create your own QuerySet that adds an authorized filter and use that. The following code comes from the Joyous events EventQuerySet and is based upon PageQuerySet.public_q and BaseViewRestriction.accept_request. It gets all the restrictions that could apply, excludes the ones that the user passes, and then filters out the pages with the remaining restrictions.
from wagtail.core.query import PageQuerySet
from wagtail.core.models import Page, PageManager, PageViewRestriction
class MyQuerySet(PageQuerySet):
def authorized_q(self, request):
PASSWORD = PageViewRestriction.PASSWORD
LOGIN = PageViewRestriction.LOGIN
GROUPS = PageViewRestriction.GROUPS
KEY = PageViewRestriction.passed_view_restrictions_session_key
restrictions = PageViewRestriction.objects.all()
passed = request.session.get(KEY, [])
if passed:
restrictions = restrictions.exclude(id__in=passed,
restriction_type=PASSWORD)
if request.user.is_authenticated:
restrictions = restrictions.exclude(restriction_type=LOGIN)
if request.user.is_superuser:
restrictions = restrictions.exclude(restriction_type=GROUPS)
else:
membership = request.user.groups.all()
if membership:
restrictions = restrictions.exclude(groups__in=membership,
restriction_type=GROUPS)
q = models.Q()
for restriction in restrictions:
q &= ~self.descendant_of_q(restriction.page, inclusive=True)
return q
def authorized(self, request):
self.request = request
if request is None:
return self
else:
return self.filter(self.authorized_q(request))
You could then set this to be your model's default QuerySet.
class MyPage(Page):
objects = PageManager.from_queryset(MyQuerySet)()
Then when filtering your MyPage objects you can say MyPage.objects.live().authorized(request).all()
Hope that is helpful. May contain bugs.

django - simplest way to customize admin search

I'm using django 1.8.
What I need is to do case insensitive admin-search in multiple fields and allow the user to use the AND, OR and NOT operators and some how group words either with parentheses or quotes.
Search Example:
cotton and (red or "dark blue")
I've already discovered django-advanced-filter and django-filter...
They are filters! I also want to allow the user to type in the keys in the search box.
I know that get_search_results allows us to override the search behaviour, but before I write a code for this, I want to ask is there a package that would do this for me?
Note that I feel that making a custom search with haystack is pretty complex.
This answer seems to work for me after performing the little edit mentioned in my comment. Yet, I have no idea whether this is the "correct" way of doing it.
Here is the updated code that works on django 1.8:
from django.contrib import admin
from django.db import models
from bookstore.models import Book
from django.contrib.admin.views.main import ChangeList
import operator
class MyChangeList(ChangeList):
def __init__(self, *a):
super(MyChangeList, self).__init__(*a)
def get_queryset(self, request):
print dir(self)
# First, we collect all the declared list filters.
(self.filter_specs, self.has_filters, remaining_lookup_params,
use_distinct) = self.get_filters(request)
# Then, we let every list filter modify the queryset to its liking.
qs = self.root_queryset
for filter_spec in self.filter_specs:
new_qs = filter_spec.queryset(request, qs)
if new_qs is not None:
qs = new_qs
try:
# Finally, we apply the remaining lookup parameters from the query
# string (i.e. those that haven't already been processed by the
# filters).
qs = qs.filter(**remaining_lookup_params)
except (SuspiciousOperation, ImproperlyConfigured):
# Allow certain types of errors to be re-raised as-is so that the
# caller can treat them in a special way.
raise
except Exception, e:
# Every other error is caught with a naked except, because we don't
# have any other way of validating lookup parameters. They might be
# invalid if the keyword arguments are incorrect, or if the values
# are not in the correct type, so we might get FieldError,
# ValueError, ValidationError, or ?.
raise IncorrectLookupParameters(e)
# Use select_related() if one of the list_display options is a field
# with a relationship and the provided queryset doesn't already have
# select_related defined.
if not qs.query.select_related:
if self.list_select_related:
qs = qs.select_related()
else:
for field_name in self.list_display:
try:
field = self.lookup_opts.get_field(field_name)
except Exception as ex:# models.FieldDoesNotExist:
print ex
pass
else:
if isinstance(field.rel, models.ManyToOneRel):
qs = qs.select_related()
break
# Set ordering.
ordering = self.get_ordering(request, qs)
qs = qs.order_by(*ordering)
# Apply keyword searches.
def construct_search(field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('#'):
return "%s__search" % field_name[1:]
else:
return "%s__icontains" % field_name
if self.search_fields and self.query:
orm_lookups = [construct_search(str(search_field))
for search_field in self.search_fields]
or_queries = []
for bit in self.query.split():
or_queries += [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
if len(or_queries) > 0:
qs = qs.filter(reduce(operator.or_, or_queries))
if not use_distinct:
for search_spec in orm_lookups:
if admin.utils.lookup_needs_distinct(self.lookup_opts, search_spec):
use_distinct = True
break
if use_distinct:
return qs.distinct()
else:
return qs
#admin.register(Book)
class AdminBookstore(admin.ModelAdmin):
list_display = ('title', 'author', 'description')
search_fields = ('title', 'author', 'description')
def get_changelist(*a, **k):
return MyChangeList

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.

Passing in Session data from Flask to WTForms

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.

Categories