I created a simple CRUD app using Flask and FlaskForm. I have a table where the last name, first name, birthdate, and sex are displayed coming from the database. Beside each name are links labeled as delete and update. When you click on update, you are routed to the update page with a form where the form fields corresponding to the last name, first name, birthdate, and sex should be populated. Sex is a select field with options Male and Female, how do I populate this based from the database? Using FlaskForm, I tried {{form.sex(value=user_to_update.sex)}} but it does not populate.
Here is my Update route:
#app.route('/update/<int:id>', methods=['GET', 'POST'])
def update(id):
form = Form()
user_to_update = TblPatient.query.get_or_404(id)
if request.method == 'POST':
user_to_update.lastname = form.lastname.data
user_to_update.firstname = form.firstname.data
user_to_update.birthdate = form.birthdate.data
user_to_update.sex = form.sex.data
db.session.commit()
return redirect(url_for('add_record'))
return render_template('update.html', form=form, user_to_update=user_to_update)
Here is the FlaskForm part:
class Form(FlaskForm):
lastname = StringField('Last Name', validators=[DataRequired()])
firstname = StringField('Firstname', validators=[DataRequired()])
birthdate = DateField('Date of Birth', format='%Y-%m-%d', validators=[DataRequired()])
sex = SelectField('Sex',
choices=['Select', 'Male', 'Female'],
default='Select',
validators=[DataRequired()])
submit = SubmitField('submit')
Here is my update.html where the form fields except for sex are populated:
<form action="{{request.path}}" method="post">
{{form.hidden_tag()}}
<fieldset>
<legend>Patient Info</legend>
<p>
{{form.lastname.label}} <br/>
{{form.lastname(value=user_to_update.lastname, size=30)}}
</p>
<p>
{{form.firstname.label}} <br/>
{{form.firstname(value=user_to_update.firstname, size=30)}}
</p>
<p>
{{form.birthdate.label}} <br/>
{{form.birthdate(value=user_to_update.birthdate)}}
</p>
<p>
{{form.sex.label}} <br/>
{{form.sex(value=user_to_update.sex)}}
</p>
</fieldset>
<br/>
{{form.submit}}
</form>
This is the home page:
When I click on update, it redirects me to the update page in question. How do I populate the sex field based on FlaskForm?
If the names of the database columns match those of the form's input fields, you can simply pass the database object to the form and the fields will be automatically populated. You do not need to pass the data to the input fields within the template.
patient = Patient.query.get_or_404(patient_id)
form = PatientForm(request.form, obj=patient)
An alternative to using the obj attribute is the assignment using the data attribute. See the documentation for the Form class.
In addition, you can then use the populate_obj function to transfer the form data to the database object.
form.populate_obj(patient)
The following example shows you how your endpoint should look like.
The patient is read from the database and assigned to the form. If the request is of type POST and the entries have been validated successfully, the patient data will be updated using the form.
#app.route('/update/<int:patient_id>', methods=['GET', 'POST'])
def update(patient_id):
patient = Patient.query.get_or_404(patient_id)
form = PatientForm(request.form, obj=patient)
if form.validate_on_submit():
form.populate_obj(patient)
db.session.commit()
return redirect(url_for('add_record'))
return render_template('update.html', **locals())
Related
I would like to do:
I am trying to create a form input on a detail view that will update a particular data column ('status') of the detailed model instance. Here is a picture of what I have in mind:
The selector would display the current status and the user could change it and update from the detail view without having to access the UpdateView.
my idea here would be to have this happen:
1. On submit, get the new user entered value.
2. get the model instance of the currently detailed class
3. assign the model instance attribute as the user entered value
4. save the model instance
I've tried: I don't know if this is the best way to do this but i've been trying to create an AJAX call, mostly by looking for examples online.
Results: Terminal shows Post on submit: "[19/Nov/2019 17:50:33] "POST /task/edit/4 HTTP/1.1" 200 41256". However, the data is not saved to the db. On refresh, the selector returns to previously saved status.
The console shows: "script is connected", and "Update Status" with no errors. On submit, the alert displays success message: "127.0.0.1:8000 says status updated".
Task_detail.html
<div class="deliv-box edit">
<form id="status-update-form" method='POST' action='{% url "task_edit" task.pk %}'>
{% csrf_token %}
{{task_form.status}}
<input id="status-update-btn" type="submit" value="Update Status" />
</form>
</div>
...
<script type="text/javascript">
var frm = $('#status-update-form');
frm.submit(function () {
console.log("script is connected")
console.log($('#status-update-btn').val())
$.ajax({
type: frm.attr('method'),
url: frm.attr('action'),
data: frm.serialize(),
success: function (data) {
$("#deliv-box edit").html(data);
alert("status updated");
},
error: function(data) {
alert("error");
}
});
return false;
});
</script>
forms.py
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = "__all__"
views.py
class TaskDetail(ModelFormMixin, DetailView):
template_name='task_detail.html'
model = Task
form_class = TaskForm
def get_context_data(self, **kwargs):
context = super(TaskDetail, self).get_context_data(**kwargs)
context['task_form'] = self.get_form
return context
def update(request):
if request.method=='POST':
task_id = request.POST.get('id')
task = Task.objects.get(pk = task_id)
status_obj = request.POST.get('status')
task.status = status_obj
task.save()
return JsonResponse({'status':'updated...'})
else:
return JsonResponse({'status':'not updated'})
thank you.
A solution:
In the unlikely event that someone stumbles across this question and who is, like me, just trying to figure it out all by themselves, here is what I've learned about how this works: When a user wants to update a form, Django pre-populates the form with the existing data related to that instance. A user can then alter the data and re-submit the form.
Here, I was attempting to alter just one field of the exiting instance, but as I was only calling that one field, Django was assuming not, as I had hoped, that the other fields would remain the same, but that I intended the other fields to be submitted as blank. Where the fields are required one cannot return that field as blank. Therefore, Django was not able to validate the form and so the form did not get updated.
A solution that works is to call all the fields as hidden and show just the one you want to alter. This way Django can return the unaltered data and validate the form, and you get an update button on your detail view:
<form method="POST">
{% csrf_token %}
<h4> STATUS: </h4>
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
{{form.status}}
<button type="submit" class="btn btn-success">submit</button>
</form>
You are overriding the method update which does not exist, so it is never called.
You need to subclass UpdateView instead of the DetailView and the mixin.
class TaskUpdateView(UpdateView):
template_name='task_detail.html'
model = Task
form_class = TaskForm
# you can use the line below instead of defining form_class to generate a model form automatically
# fields = ('status', )
def form_valid(self, form):
post = form.save(commit=False)
# do anything here before you commit the save
post.save()
# or instead of two lines above, just do post = form.save()
return JsonResponse({'status':'updated...'})
Here is how you would add readonly (disabled) fields to your form:
class TaskForm(forms.ModelForm):
# override the default form field definitions for readonly fields
other_field = forms.CharField(disabled=True)
another_field = forms.IntegerField(disabled=True)
class Meta:
model = Task
fields = ("status", "other_field", "another_field")
# you could also just do:
# fields = '__all__'
I have a SelectField that is populated from a database table. I load the choices into the form as follows:
#statuses.route('/new', methods=['GET', 'POST'])
#login_required
def new_status():
form = StatusForm()
form.status_cd.choices = [(a.id, a.status_cd) for a in \
Status_Code.query.order_by('status_cd')]
if form.validate_on_submit():
status = Status(author=current_user)
form.to_model(status)
db.session.add(status)
db.session.commit()
flash('The status was added successfully.')
return redirect(url_for('.index'))
return render_template('statuses/new_status.html', form=form)
The model referenced in the query is as follows:
class Status_Code(db.Model):
__tablename__ = 'status_cd'
id = db.Column(db.Integer, primary_key=True)
status_cd = db.Column(db.String(16), nullable=False)
status_detail = db.Column(db.Text)
is_option_active = db.Boolean()
date_created = db.Column(db.DateTime, default=db.func.now())
And the form class is as follows:
class StatusForm(Form):
datetime = DateTimeField('Date / Time')
name = StringField('Name', validators=[Required()])
status_cd = SelectField('Status Code', coerce=int)
status_detail = TextAreaField('Status Detail', default="Default text",\
validators=[Required()])
submit = SubmitField('Submit')
The Question
Depending on the option selected in the SelectField, I want it to dynamically set the status_detail TextAreaField's default text. The value from the SelectField should query the database and return the status_detail, which should be the default text.
For example, if I have:
id status_cd status_detail
1 Apple The apples are red.
2 Banana The bananas are yellow.
If the user selects the "Apple" option, the default text should be "The apples are red."
Any assistance would be greatly appreciated! I am building my first Flask app so I'm new to this.
In my experience with flask, you can do this a couple of ways. There is no right way, and it is all up to you:
You can load the status_detail data, and place it in a data-detail tag in your select option value:
<select name='status_cd' onchange="get_status(this);">
{% for s in status %}
<option value='{{ s.id }}' data-detail='{{ s.status_detail }}'>{{ s.status_cd }} </option>
{% endfor %}
</select>
Then you can do an onchange with JavaScript, which can then get the data-detail value and update your text box (this is pseudo code, not meant for copy and paste):
<script>
function onchange(o){
var value = $(o).attr('data-detail');
//do something with the value
}
</script>
OR
You can do it where it pulls from the database dynamically if you don't wan to put the data-detail tag in your code, like this:
Same onchange with JavaScript, but can then do a call to an Ajax call to your routed method to return your value (this is pseudo code, not meant for copy and paste):
<script>
function onchange(o){
var value = $(o).value();
$.ajax({
url: '/my_rout',
data: value,
success : function(data){
//handle your result here
}
})
}
</script>
I hope this at least gets you in the right direction with some different options to choose from.
I have the three models User (django.contrib.auth), Screening and User_Screening. The User_Screening is a m2m table with the extra field status.
#models.py
from django.db import models
from django.contrib.auth.models import User
class Screening(models.Model):
title = models.CharField(max_length=255)
start = models.DateTimeField()
user_relation = models.ManyToManyField(User, blank=True,
through='User_Status')
class User_Status(models.Model):
ATTENDING = 'c'
NOT_ATTENDING = 'n'
PROJECTION = 'p'
STATUS_CHOICES = (
(ATTENDING, 'attending'),
(NOT_ATTENDING, 'not attending'),
(PROJECTING, 'projecting'),
)
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
Now I want to make a view, which shows all upcoming screenings. So far, so easy:
#views.py
#login_required()
def index(request):
current_screenings = Screening.objects.filter(start__gte=timezone.now())
context = {'current_screenings': current_screenings}
return render(request, 'schedule/index.html', context)
In this view, logged in users should be able, to update their status (from the User_Screening table). It could also be, that the user does not yet have a record for this screening, so one should be created.
I don't understand, how I could archive a form dropdown field for each screening, where the user can select his status. (Either ? if no status is set yet, attending, not attending or projection)
From what I understand I need multiple forms, that are aware what screening they are related to.
Also, Formsets seem not to work, because I can't always fill a form with initial data, as there could be records missing for some or all screenings. Furthermore I would not know, which form belongs to which of the screening objects.
Update:
What I want to end up with in HTML is something like this:
<form>
<h1>Current Screening 1</h1>
<select onchange="submit()" name="screening_user" id="s1">
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option selected="selected" value="pro">Projection</option>
</select>
<h1>Current Screening 2</h1>
<select onchange="submit()" name="screening_user" id="s2">
<!-- The 'Please Select' option is only visible, if the user does not
have a relation in 'User_Screening' for this screening -->
<option selected="selected" value="none">Please Select</option>
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option value="pro">Projection</option>
</select>
<!-- More Screenings -->
<h1>Current Screening n</h1>
<!-- select for screening n -->
</form>
Therefore a changing amount of forms is needed, from the same form with preloaded data according to the logged in user.
If a screening has a m2m relation to Users, than the attending users can be in that list. If not in attending... Well, than they are not attending! Does that make sense?
class Screening(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField()
attending = models.ManyToManyField(User)
Form:
class ScreeningForm(ModelForm):
class Meta:
model = Screening
fieds = ['attending', ]
Formset:
ScreeningFormSet = modelformset_factory(Screenig, max_num=1)
formset = ScreeningFormSet(Screening=Screening.objects.filter(date__gte=now))
On one hand you could send the form data via an ajax request. In that request you would simply send one form and process the data. You would not need any formsets. Depending on your usecase this may add unnecessary traffic to your server.
Another solution would be to add another STATUS_CHOICE like 'nothing selected' as default value for the form that is used if there is no entry for the Screening User combination in the db. In the POST handler of your view you you can then just check if the form data is set to this value. In this case you simply ignore the form. If it is another value, then you set the db entry accordingly.
With some help from #django on feenode, I solved my problem. In the end, I stuck with formsets.
Considering the models.py from my question I had to change User_Status slightly, adding a NO_STATUS choice for the Select-Widget if no relation yet exist for the screening. Note that NO_STATUS is not a choice for the model.CharField!
#models.py
class User_Status(models.Model):
NO_STATUS = '?'
PROJECTIONIST = 'p'
ATTENDING = 'c'
NOT_ATTENDING = 'n'
STATUS_CHOICES = [
(ATTENDING, 'Anwesend'),
(NOT_ATTENDING, 'Nicht anwesend'),
(PROJECTIONIST, 'Vorführer'),
]
STATUS_CHOICES_AND_EMPTY = [(NO_STATUS, 'Please choose')] + STATUS_CHOICES
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES,
default=ATTENDING)
Next up, the form. The modified __init__ takes care, that 'Please choose' is only a valid choice, if that is set as the initial value for status. Otherwise, the choice is just not displayed.
#forms.py
class ScreeningUserStatusForm(forms.Form):
screening_id = forms.IntegerField(min_value=1)
status = forms.ChoiceField(choices=User_Status.STATUS_CHOICES_AND_EMPTY,
widget=forms.Select(attrs={"onChange":'submit()'}))
def __init__(self, *args, **kwargs):
super(ScreeningUserStatusForm, self).__init__(*args, **kwargs)
if self['status'].value() != User_Status.NO_STATUS:
#Once, a status is selected, the status should not be unset.
self.fields['status'].choices=User_Status.STATUS_CHOICES
Finally the view, that uses a formset to put all current screenings in it.
def update_user_status(screening, user, status):
#Get old status, if already exists.
new_status = User_Status.objects.get_or_create(screening=screening,
user=user)
# Add to selected status
new_status.status = status
new_status.save()
#login_required()
def index(request):
"""
displays all upcoming screenings
"""
# Get current screenings
current_screening_set = Screening.objects.filter(start__gte=timezone.now() - datetime.timedelta(hours=24)).order_by('start')
current_screening_list = current_screening_set.values('id')
ScreeningFormSet = formset_factory(ScreeningUserStatusForm, extra=0)
if request.method == 'POST':
#Get a formset bound to data from POST
formset = ScreeningFormSet(request.POST, request.FILES)
if formset.is_valid():
for form in formset.cleaned_data:
s = get_object_or_404(Screening, pk=form['screening_id'])
if form['status'] != User_Status.NO_STATUS:
update_user_status(screening=s, user=request.user, status=form['status'])
else:
#create a fresh formset
for form_data in current_screening_list:
screening = Screening.objects.get(pk=form_data['id'])
status = User_Status.objects.filter(user=request.user, screening=screening)
if status.count() != 1:
form_data['status'] = u'?'
else:
form_data['status'] = status.first().status
form_data['screening_id'] = form_data['id']
formset = ScreeningFormSet(initial=current_screening_list)
forms_and_curr_screenings = zip(formset.forms, current_screening_set)
context = {'formset' : formset, 'current_screenings' : forms_and_curr_screenings}
return render(request, 'schedule/index.html', context)
The formset.forms are zipped together with the current_screening_set, to provide additional data to each from. formset is additionally given to the template for the management_form.
A template could look like this
<!-- index.html -->
{% if current_screenings %}
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table>
<thead>
<tr>
<th>Screening</th>
<th>My Status</th>
</tr>
</thead>
<tbody>
{% for form, screening in current_screenings %}
<tr>
<td>{{ screening }}</a></td>
<td>
{{ form.screening_id.as_hidden }}
{{ form.status }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% endif %}
I'm new to Django and I'm creating an app to create and display employee data for my company.
Currently the model, new employee form, employee table display, login/logout, all works. I am working on editing the current listings.
I have hover on row links to pass the pk (employeeid) over the url and the form is populating correctly- except the manytomanyfields are not populating, and the pk is incrementing, resulting in a duplicate entry (other than any data changes made).
I will only put in sample of the code because the model/form has 35 total fields which makes for very long code the way i did the form fields manually (to achieve a prettier format).
#view.py #SEE EDIT BELOW FOR CORRECT METHOD
#login_required
def employee_details(request, empid): #empid passed through URL/link
obj_list = Employee.objects.all()
e = Employee.objects.filter(pk=int(empid)).values()[0]
form = EmployeeForm(e)
context_instance=RequestContext(request) #I seem to always need this for {%extend "base.html" %} to work correctly
return render_to_response('employee_create.html', locals(), context_instance,)
#URLconf
(r'^employee/(?P<empid>\d+)/$', employee_details),
# snippets of employee_create.html. The same template used for create and update/edit, may be a source of problems, they do have different views- just render to same template to stay DRY, but could add an additional layer of extend for differences needed between the new and edit requests EDIT: added a 3rd layer of templates to solve this "problem". not shown in code here- easy enough to add another child template
{% extends "base.html" %}
{% block title %}New Entry{% endblock %}
{% block content %}
<div id="employeeform">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="/newemp/" method="post" class="employeeform">{% csrf_token %} #SEE EDIT
<div class="left_field">
{{ form.employeeid.value }}
{{ form.currentemployee.errors }}
<label for="currentemployee" >Current Employee?</label>
{{ form.currentemployee }}<br/><br/>
{{ form.employer.errors }}
<label for="employer" class="fixedwidth">Employer:</label>
{{ form.employer }}<br/>
{{ form.last_name.errors }}
<label for="last_name" class="fixedwidth">Last Name:</label>
{{ form.last_name }}<br/>
{{ form.facility.errors }} #ManyToMany
<label for="facility" class="fixedwidth">Facility:</label>
{{ form.facility }}<br/><br/>
</div>
<div id="submit"><br/>
<input type="submit" value="Submit">
</div>
</form>
#models.py
class Employee(models.Model):
employeeid = models.AutoField(primary_key=True, verbose_name='Employee ID #')
currentemployee = models.BooleanField(null=False, blank=True, verbose_name='Current Employee?')
employer = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
facility = models.ForeignKey(Facility, null=True, blank=True)
base.html just has a header on top, a menu on the left and a big empty div where the forms, employee tables, etc all extend into.
screenshot2
So, how do I need to change my view and/or the in the template to update an entry, rather than creating a new one? (
And how do I populate the correct foriegnkeys? (the drop down boxes have the right options available, but the "-----" is selected even though the original database entry contains the right information.
Let me know if i need to include some more files/code
I have more pics too but i cant link more or insert them as a new user :< I'll just have to contribute and help out other people! :D
EDIT:
I've been working on this more and haven't gotten too far. I still can't get the drop-down fields to select the values saved in the database (SQLite3).
But the main issue I'm trying to figure out is how to save as an update, rather than a new entry. save(force_update=True) is not working with the default ModelForm save parameters.
views.py
def employee_details(request, empid):
context_instance=RequestContext(request)
obj_list = Employee.objects.all()
if request.method == 'POST':
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(request.POST, instance=e)
if form.is_valid():
form.save()
return HttpResponseRedirect('/emp_submited/')
else:
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(instance=e)
return render_to_response('employee_details.html', {'form': form}, context_instance,)
also changed template form action to "" (from /newemp/ which was the correct location for my new employee tempalte, but not the update.
Thanks to this similar question.
updating a form in djnago is simple:
steps:
1. extract the previous data of the form and populate the edit form with this these details to show to user
2. get the new data from the edit form and store it into the database
step1:
getting the previous data
views.py
def edit_user_post(request, topic_id):
if request.method == 'POST':
form = UserPostForm(request.POST)
if form.is_valid():
#let user here be foreign key for the PostTopicModel
user = User.objects.get(username = request.user.username)
#now set the user for the form like: user = user
#get the other form values and post them
#eg:topic_heading = form.cleaned_data('topic_heading')
#save the details into db
#redirect
else:
#get the current post details
post_details = UserPostModel.objcets.get(id = topic_id)
data = {'topic_heading':topic.topic_heading,'topic_detail':topic.topic_detail,'topic_link':topic.topic_link,'tags':topic.tags}
#populate the edit form with previous details:
form = UserPostForm(initial = data)
return render(request,'link_to_template',{'form':form})
Hello I am trying to display work orders from my mysql database to show up in an email. However There is a problem because work_orders is a part of my Class Invoice manytomany field. This gives me this error.
'ManyRelatedManager' object has no attribute 'description'
I not really sure what the problem is. Here are some part of my app that could be helpful.
#views.py
#login_required
def invoice_mail(request, id=1):
invoices_list = Invoice.objects.filter(pk=id)
invoice = get_object_or_404(Invoice, pk=id)
client = invoices_list[0].client
invoice_no = invoices_list[0].invoice_no
date = invoices_list[0].date
work_orders = invoices_list[0].work_orders
t = loader.get_template('registration/email.txt')
c = Context({
'client': client.company,
'address':client.address,
'city': client.city,
'postcode': client.postcode,
'email': client.email,
'date': date,
'invoice_no': invoice_no,
'work_orders': work_orders.description,
})
send_mail('Welcome to My Project', t.render(c), 'jess#example.com', ['tom#example.com'], fail_silently=False)
return render_to_response('sent_email.html', locals(), context_instance=RequestContext(request))
email.txt
INVOICE
Bill to: INVOICE # {{invoice_no}}
{{client}} DATE: {{date}}
{{address}}
{{city}}
{{postcode}}
{{email}}
quantity item Description
{{work_orders.description}}
Unless you added a description field to the manager, the attribute doesn't exist (as it says).
Maybe you want to use
for order in work_orders.all():
print order.description
or in a template
{% for order in work_orders.all }}
{{ order.description }}
{% endfor %}
And maybe you need to change it in the context
'work_orders': work_orders
work_orders is not a list. It's an accessor/ORM Manager for your many-to-many field.
To get the actual work orders, you need to do work_orders.all() (or .filter(foo=bar)) and then iterate over each work order you get back to format them decently for including in an email