Construct a form on the basis of row inside database. Django - forms

# models.py
from django.db import models
from django.forms import ModelForm
question_choices = (
(1, 'yes/no'),
(2,'text'),
(3,'numberic'),
)
class Question(models.Model):
title = models.CharField(max_length=500,blank=False)
responseType = models.SmallIntegerField(choices=question_choices,blank=False
,default=2)
mandatory = models.BooleanField(default=True)
def __str__(self):
return self.title
answer_choices = (
(1, 'YES'),
(2,'NO'),
)
class Response(models.Model):
questionId = models.ForeignKey(Question,on_delete=models.CASCADE)
answerType1 = models.CharField(max_length=4,choices=answer_choices,blank=True)
answerType2 = models.TextField(max_length=500,blank=True)
answerType3 = models.IntegerField(blank=True)
def __str__(self):
return self.id
Assume two buttons on UI. 1. Create questions 2. Fill Questions.
When user will click on 1st button(Create questions), according to Question model user will be able to save some question to the DB.
When user clicks on 2nd button (Fill Questions)
If data is there in DB then , create a form according to row of Question model and send it to user such that after getting the response from user data will be saved inside Response model.
For reference i have added screenshot of Question model from admin pannel.
I wanted to create a form according to every row.

Related

Flask: How can I store user's form-input so that it can be retrieved two requests later

I am working on a logistics website using Flask.
On route /step1 the user fills in a form detailing n parcels and their attributes.
The form is submitted and a POST request is sent to /step2 with the following data:
*weight1, length1, width1, height1,
weight2, length2 ...
...
...
*weightn, lengthn, widthn, heightn,
In /step2 the user fills in the sender and recipient addresses.
This form is submitted as a POST request to /process-order
Here's the problem:
How does my process_order view retrieve the data provided in both /step1 and /step2
I can think of three solutions - listed below - but I only know how to go about the first one.
storing the data submitted in step1 form inside hidden inputs and sending this to the form in step2.html and subsequently retrieving this data from the request sent to process_order and storing them as an array.
Caching
flask.g
N.B. This is my very first Flask project and I have zero experience with caching. So please dumb things down for me.
Update
Here's my actual code:
#main.route('/order', methods=['POST'])
def order():
# this function is invoked when user submits a form from the home page
country_from = request.form['country_from']
country_to = request.form['country_to']
data = request.form
parcels = []
for i in range(1, int(data['parcel_count']) + 1):
parcel = Parcel(
weight=data['weight{}'.format(i)],
length=data['length{}'.format(i)],
width=data['width{}'.format(i)],
height=data['height{}'.format(i)],
)
parcels.append(parcel)
weight_units = data['units']
if weight_units == 'kg':
distance_units = 'cm'
else:
distance_units = '"'
return render_template('order.html',
countries = COUNTRIES,
country_from = country_from,
country_to = country_to,
parcels = parcels,
weight_units = weight_units,
distance_units = distance_units,
)
#main.route('/process-order', methods=['POST'])
def process_order():
data = request.form
parcels = []
# pending
# how can I retrieve the parcels array again?
Note that my view functions are being invoked from the action attribute of the forms in my templates (not included here).
Also, you can pass the inputs between steps as parameters as follows
#app.route('/step1', methods=['GET', 'POST'])
def step1():
parcels=[(11,12,13,14),(21,22,23,24),(31,32,33,34)] # contain list of weights, lengths, widths, heights,
weight_units ='kg' # another data to pass
return redirect(url_for('step2',x=parcels,y=weight_units ))
#app.route('/step2/<x>/<y>', methods=['GET', 'POST'])
def step2(x,y):
print(x) # console output
print(y)
return 'ok'
and so on, send all to the next one with more parameters

Secondary Table Attributes in Create / Edit Views for primary models

I want to be able to create and edit the secondary table attributes (the relational table) of a many-to-many relationship during the creation or editing of either of the primary tables. So, when I edit one of the primary tables and add a relation to another model (implicitly using the secondary table), I want to be able to access / edit the attributes of that secondary relationship.
More specifically:
Models
# "Primary" table
class Paper(db.Model):
__tablename__ = 'papers'
...
chapters = db.relationship(Chapter, secondary="chapter_paper")
...
# "Primary" table
class Chapter(db.Model):
...
papers = db.relationship('Paper', secondary="chapter_paper")
...
# "Secondary" table
class ChapterPaper(db.Model):
__tablename__ = 'chapter_paper'
paper_id = db.Column(db.Integer,
db.ForeignKey('papers.id'),
primary_key=True)
chapter_id = db.Column(db.Integer,
db.ForeignKey('chapters.id'),
primary_key=True)
### WANT TO EDIT
printed = db.Column(db.Boolean, default=False)
note = db.Column(db.Text, nullable=True)
### WANT TO EDIT
paper = db.relationship('Paper',
backref=db.backref("chapter_paper_assoc",
lazy='joined'),
lazy='joined')
chapter = db.relationship(Chapter,
backref=db.backref("chapter_paper_assoc",
lazy='joined'),
lazy='joined')
So, for this example, I want to be able to edit the "printed" and "note" attribute of ChapterPaper from the create / edit forms of Paper and Chapter in flask admin.
ModelViews
# MainModelView subclasses flask_admin.contrib.sqla.ModelView
class PaperModelView(MainModelView):
...
form_columns = (
'title',
'abstract',
'doi',
'pubmed_id',
'link',
'journals',
'keywords',
'authors',
'chapters',
)
# Using form_columns allows CRUD for the many to many
# relation itself, but does not allow access to secondary attributes
...
So, I honestly have very little idea of how to do this. If I added the form fields as extras and then manually validated them...? (I don't know how to do this)
Even then, adding extra fields to the form doesn't really cover multiple models. Can anyone show me how to do this, or point me to a tutorial / even a relevant example from code that's part of some random project?
Thanks!
Alrighty, this was a lot of work and required a lot of RTFM, but it was pretty straightforward once I got going.
The way to do this without a neat API is to extend the model view and replace the create / edit form with a form of your own.
Here is my form class:
class ExtendedPaperForm(FlaskForm):
title = StringField()
abstract = TextAreaField()
doi = StringField()
pubmed_id = StringField()
link = StringField()
journals = QuerySelectMultipleField(
query_factory=_get_model(Journal),
allow_blank=False,
)
issue = StringField()
volume = StringField()
pages = StringField()
authors = QuerySelectMultipleField(
query_factory=_get_model(Author),
allow_blank=False,
)
keywords = QuerySelectMultipleField(
query_factory=_get_model(Keyword),
allow_blank=True,
)
chapters_printed = QuerySelectMultipleField(
query_factory=_get_model(Chapter),
allow_blank=True,
label="Chapters (Printed)",
)
chapters = QuerySelectMultipleField(
query_factory=_get_model(Chapter),
allow_blank=True,
label="Chapters (All)",
)
The important part for making this functionality happen is the on_model_change method, which performs an action before a model is saved.
...
def on_model_change(self, form, model, is_created):
"""
Perform some actions before a model is created or updated.
Called from create_model and update_model in the same transaction (if it has any meaning for a store backend).
By default does nothing.
Parameters:
form – Form used to create/update model
model – Model that will be created/updated
is_created – Will be set to True if model was created and to False if edited
"""
all_chapters = list(set(form.chapters.data + form.chapters_printed.data))
for chapter in all_chapters:
if chapter in form.chapters_printed.data: # if chapter in both, printed takes priority
chapter_paper = ChapterPaper.query.filter_by(chapter_id=chapter.id, paper_id=model.id).first()
if not chapter_paper:
chapter_paper = ChapterPaper(chapter_id=chapter.id, paper_id=model.id)
chapter_paper.printed = True
db.session.add(chapter_paper)
journal = None
if form.journals.data:
journal = form.journals.data[0]
if journal: # Assumes only 1 journal if there are any journals in this field
issue = form.issue.data
volume = form.volume.data
pages = form.pages.data
journal_paper = JournalPaper.query.filter_by(journal_id=journal.id, paper_id=model.id).first()
if not journal_paper:
journal_paper = JournalPaper(journal_id=journal.id, paper_id=model.id)
journal_paper.issue = issue
journal_paper.volume = volume
journal_paper.pages = pages
db.session.add(journal_paper)
...

Limiting a Django form's ManyToManyField queryset in a formtools wizard based on selection on previous form

I'm using a SessionWizardView from django-formtools to construct a two-form wizard. The challenge I'm facing is that I need to reference the input from the first form to limit the available querysets on the second form.
To make it more interesting, I'm using crispy forms for layout and the queryset needs to be limited by a method on a related item.
Here's the (much simplified) gist of where I'm at:
Models
class Product(models.Model):
# pk, name, etc....
catalogitem = ForeignKey("myapp.CatalogItem")
colors = ManyToManyField("myapp.Colors")
class Colors(models.Model):
# pk, name, etc....
class CatalogItem(models.Model):
# Colors are stored within CatalogVariants, which I've left
# as a blackbox in this example, since they are retrieved as
# a queryset on this model with this method:
# pk, name, etc....
def get_colors(self):
# Returns a queryset of color objects.
Views
ProductFormWizard(SessionWizardView):
form_list = [
productFormWizard_Step1,
productFormWizard_Step2,
]
def get_context_data(self, **kwargs):
# ...
pass
def get_form_initial(self, step):
initial = {}
# ...
return self.initial_dict.get(step, initial)
def process_step(self, form):
if self.steps.step1 == 1:
pass
return self.get_form_step_data(form)
def done(self, form_list, **kwargs):
return render(self.request, 'done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Forms
productFormWizard_Step1(forms.ModelForm):
# Defines a form where the user selects a CatalogProduct.
model = Product
productFormWizard_Step2(forms.ModelForm):
"""
Defines a form where the user chooses colors based on
the CatalogProduct they selected in the previous step.
"""
model = Product
Based on research via the Googles and some SO questions (none of which were =directly= related), I'm assuming I need to set the .queryset property on the colors field, but I'm not exactly sure where to do that. Two thoughts:
I would guess it goes in .get_form_initial() somehow, but I'm at a loss as to the best way to achieve that.
Alternatively, the appropriate code might go into the productFormWizard.get_context_data() method somehow.
Within .get_form_initial(), I can do something like this:
if step == '1':
itemID = self.storage.get_step_data('0').data.get('0-pfProduct', "")
if itemID:
obj = CatalogItem.objects.get(id=itemID)
initial['colors'] = obj.get_get_colors()
However, this just selects the available related items... it doesn't limit the list.
Additional Info
Python == 3.5.3
Django == 1.10.6
django-crispy-forms == 1.6.1
django-formtools == 2.0
The solution is to override the .get_form() method on the View:
def get_form(self, step=None, data=None, files=None):
form = super(bzProductFormWizard, self).get_form(step, data, files)
if step == '1':
past_data = self.get_cleaned_data_for_step('0')
product = past_data['product']
form.fields['colors'].queryset = ... #CUSTOM QUERYSET
return form

Adding columns to a Web2py table in a form

In my web2py application, in the controller I read from an external DB the names of students I want to take a register for. I loop through the resulting list adding the list elements to a new list.
for student in pupils_query:
attendance_list.insert(counter, [student[0], student[1], student[2], student[3]])
counter += 1
counter = 0
Then for each student I read their attendance codes for the day so far from another table, and append them to attendance_list:
for attendance_code in attendance_result:
attendance_list[counter].append(attendance_code)
Now, I'm going to want to make a form from all this, using a table which will show each students' attendance code in a text input (so they can be updated if wrong), then have a dropdown for input of the current lesson code.
I'm using a FORM and TABLE helper to create the table in the form:
form=FORM(TABLE(*[TR(*rows) for rows in attendance_list]))
but can't seem to be able to add a new 'row' form item with something like:
select = "SELECT("+ main_reg_list +")"
attendance_list[counter].append(select)
where main_reg_list is dictionary of acceptable attendance codes (or of course, any other form input element).
In summary, I'm stuck adding new TDs to a table made with a TABLE helper from a list of lists. I bet I'm not the first person to overcome this problem.
I am still not clear about what you want. I think you want table of student information and in one column you want dropdown. Something similat to following image
Above form is created from following code.
I hope following code will help you:
# controller/default.py
def index():
# Dummy attendance list, list after appending attendance code
attendance_list = [['stud_id_1', 'first_name_1', 'last_name_1', 'attendance_code_1'],
['stud_id_2', 'first_name_2', 'last_name_2', 'attendance_code_2'],
['stud_id_3', 'first_name_3', 'last_name_3', 'attendance_code_5'],
['stud_id_4', 'first_name_4', 'last_name_4', 'attendance_code_4']]
possible_att_code = ['attendance_code_1', 'attendance_code_2', 'attendance_code_3', 'attendance_code_4', 'attendance_code_5']
# initialise form_rows with Table heading
form_rows = [THEAD(TR(TH('ID'), TH('First Name'), TH('Last Name'), TH('Attendence Code')))]
for attendance in attendance_list:
attendance_code_dropdown = _get_dropdown(attendance[0], attendance[3], possible_att_code)
td_list = [TD(attendance[0]), TD(attendance[1]), TD(attendance[2]),
TD(attendance_code_dropdown)]
table_row = TR(td_list, _id='row_' + attendance[0])
form_rows.append(table_row)
# Form submit button
form_rows.append(TR(INPUT(_type='submit')))
form = FORM(TABLE(*form_rows), _name='student_attendance',
_id='student_attendance')
if form.accepts(request, session):
# Write code to update record
pass
return dict(form=form)
def _get_dropdown(stud_id, att_code, possible_att_code):
option_list = []
for pac in possible_att_code:
if pac == att_code:
option_list.append(OPTION(pac, _value=pac, _selected='selected'))
else:
option_list.append(OPTION(pac, _value=pac))
return SELECT(*option_list, _name=stud_id)
<!-- views/default/index.html -->
{{extend 'layout.html'}}
{{=form}}
Are my assumptions correct? or you want any thing else? Comment if didn't understood code.

How to populate zend form field using session?

I am using sessions to populate a multi select box with options in my Zend application.
The user selects one or more options and fills in other fields on the form and then submits. If the user didn't select all of the options in the multi select then the form is displayed again but the multi select only has the options that the user did not select the last time. This process goes on until there are no more options from the multi select left to process.
Here is the code I use to get rid of the options that have already been processed so that they are not used to populate the multi select box:
if($form_successful){
// TODO remove $post['keyword_names'] (i.e. already processed) from $keyword_names (that come from $_SESSION)
$keyword_names = array_diff($keyword_names, $post['keyword_names']);
print_r($keyword_names);
if(is_array($keyword_names) && !empty($keyword_names)){
// save updated $keyword_names into $_SESSION['workflow1']
$session = new Zend_Session_Namespace('workflow1');
$session->keyword_names = $keyword_names;
// set flag to false so that we display form again
$form_successful = false;
}else{ // all keywords have been assigned
// go to next step
$this->_redirect('/workflow-1/step-'.($step+1).'/');
}
}
print_r($keyword_names); displays the correct options, however when the form is loaded when the user submits, the multi select displays the options that were there from the begining ie the options the user has just selected and submitted are not being taken out of the multi select, it is only when the user submits the form again then the multi select box updates.
Appreciate the help.
Solved the issue by making use of URL parameters. Here is the code (might differ a lot from what I posted first because some big changes were made):
// after successful form submission
if($form_successful){
// remove $post['keyword_names'] (i.e. already processed) from $keyword_names (that come from $_SESSION)
$keyword_names = array_diff($keyword_names, $post['keyword_names']);
// save remaining $keyword_names into $_SESSION['workflow1']
$session = new Zend_Session_Namespace('workflow1');
$session->keyword_names = $keyword_names;
if(is_array($keyword_names) && !empty($keyword_names)){
// redirect to the same step again - to ensure that the form will reflect (in select lists) newly created AdGroup and/or Campaign
// GET parameteres ($params_array) provide a way to remember user's choice
$params_array = array();
if(!empty($post['match_type_id'])){
$params_array['match_type_id'] = $post['match_type_id'];
}
if(!empty($post['with_permutations'])){
$params_array['with_permutations'] = $post['with_permutations'];
}
if(!empty($ad_group_id)){
$params_array['ad_group_id'] = $ad_group_id;
}
$this_step_url = UrlUtils::assemble('', $this->getRequest()->getActionName(), $this->getRequest()->getControllerName(), $this->getRequest()->getModuleName(), $params_array);
$this->_redirect($this_step_url);
}else{ // all keywords have been assigned
// go to next step
$this->_redirect('/workflow-1/step-'.($step+1).'/');
}
}
So you don't have any code about Zend_Form object here. How do you populate the form element? If you post your class code which extends Zend_Form (or any other code dials with your form) then I may help. But in any case you can populate your multiselectbox with setMultiOptions() method or addMultiOption() for each item in multiselectbox.