Model Forms in Django - django

I am doing a web interface using HTML and django. My problem is that I want to show a form in the website so that the user can fill the fields of one of the models. Although I've read some examples and documentation about how to do it, I cannot see the form in my website.
In my django app, I have the following model:
class Signature(models.Model):
sig = models.ForeignKey(Device)
STATE = models.CharField(max_length=3, choices=STATE_CHOICES)
interval = models.DecimalField(max_digits=4, decimal_places=2)
As the form is related with that model, I have created this ModelForm class:
class SignatureForm(ModelForm):
class Meta:
model = Signature
After that, I have define the following view:
def SigEditor(request):
if request.method == 'POST':
form = SignatureForm(request.POST)
if signature_form.is_valid():
# Create a new Signature object.
signature_form.save()
return HttpResponseRedirect('eQL/training/form.html')
else:
form = SignatureForm()
return render_to_response('eQL/training/showImage.html',
{'signature_form' : SignatureForm })
Finally, I can represent the form in my website by adding:
<fieldset><legend>Device Information</legend>
<form action="" method="post">
{{ signature_form }} < br>
<input type="submit" value="Submit">
</form>
</fieldset>
If I open the website, there are no errors but I don't see the form. However, before the submit button, appears:
< class 'eQL.models.SignatureForm' >
Can anyone help me? I am quite new in this web framework. Thanks!!

Update:
you have 2 problems here:
1st mistake: you name the form instance with 2 names depending on form method (but this would have raised an exception if it's not for the 2nd error you made)
2nd error: you should give the form instance to the template,
not the class ('signature_form' : SignatureForm):
return render_to_response('eQL/training/showImage.html',
{'signature_form' : form})

The template tag {{signature_form}} is expanded as < class 'eQL.models.SignatureForm' > because it is a class not an object.
You write a reference to the class not an object instance
You have to write :
return render_to_response('eQL/training/showImage.html', {'signature_form' : form})
Instead of :
return render_to_response('eQL/training/showImage.html', {'signature_form' : SignatureForm})

Related

Django forms: object has no attribute cleaned data

I'm fairly new to Django. I have been getting this error when I try to use form.cleaned_data. Everywhere I've checked online says that this is because you might not be using the is_valid function which I am. My objective is to create a set of records which stores the student ID and the subject ID as foreign keys in a model called Sub_and_stu. I'm open to any other (as simple?) ways of achieveing this.
views.py
#csrf_protect
def select_subs(request):
form1 = Sub_selection1(request.POST)
form2 = Sub_selection2(request.POST)
form3 = Sub_selection3(request.POST)
form4 = Sub_selection4(request.POST)
user = request.user
if form1.is_valid and form2.is_valid and form3.is_valid and form4.is_valid:
data1 = form1.cleaned_data
sub1=data1['subject_id']
Sub_and_stu.objects.create(student_id=user,subject_id=sub1)
data2 = form2.cleaned_data
sub2=data2['subject_id']
Sub_and_stu.objects.create(student_id=user,subject_id=sub2)
data3 = form3.cleaned_data
sub3=data3['subject_id']
Sub_and_stu.objects.create(student_id=user,subject_id=sub3)
data4 = form4.cleaned_data
sub4=data4['subject_id']
Sub_and_stu.objects.create(student_id=user,subject_id=sub4)
context={'form1':form1,'form2':form2,'form3':form3,'form4':form4}
return render(request,'select_subs.html',context)
models.py
class Subject(models.Model):
subject_id=AutoSlugField(unique=True)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Sub_and_stu(models.Model):
record_id=AutoSlugField(unique=True)
student_id=models.ForeignKey(User,on_delete=models.CASCADE)
subject_id=models.ForeignKey(Subject,on_delete=models.CASCADE)
class Sub_and_teachers(models.Model):
record_id=AutoSlugField(unique=True)
teacher_id=models.ForeignKey(User,on_delete=models.CASCADE)
subject_id=models.ForeignKey(Subject,on_delete=models.CASCADE)
forms.py
class Sub_selection1(ModelForm):
class Meta:
model = Sub_and_stu
fields=['subject_id']
#Other subject selection forms are basically a copy of this. I need the user to pick multiple subejcts at the same time
snippet from select_subs.html
<form>
{%csrf_token%}
<h3>Subject 1</h3>
{% render_field form1.subject_id class='pure-input-1 pure-input-rounded' }
<h3>Subject 2</h3>
{% render_field form2.subject_id class='pure-input-1 pure-input-rounded' }
<h3>Subject 3</h3>
{% render_field form3.subject_id class='pure-input-1 pure-input-rounded' }
<h3>Subject 4</h3>
{% render_field form4.subject_id class='pure-input-1 pure-input-rounded' }
<button type="submit" class="pure-button pure-button-primary"
name='subjects'>Submit</button>
</form>
You should be calling is_valid() with the parentheses.
Right now you are just checking if there is an attribute is_valid, which the existence of the method means it is True.
is_valid is the method
is_valid() with the parentheses is how you call the method to actually see if the form is valid or not. When you call the method it generates the cleaned_data for you.
You can read more about the is_valid method in the documentation https://docs.djangoproject.com/en/4.0/ref/forms/api/#django.forms.Form.is_valid

How to integrate a form into a Detail View?

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__'

Enforce form field validation & display error without render?

I'm a django newbie so a verbose answer will be greatly appreciated. I'm enforcing a capacity limit on any newly created Bottle objects in my model, like so:
class Bottle(models.Model):
name = models.CharField(max_length=150, blank=False, default="")
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name="bottles")
vintage = models.IntegerField('vintage', choices=YEAR_CHOICES, default=datetime.datetime.now().year)
capacity = models.IntegerField(default=750,
validators=[MaxValueValidator(2000, message="Must be less than 2000")
,MinValueValidator(50, message="Must be more than 50")])
My BottleForm looks like so:
class BottleForm(ModelForm):
class Meta:
model = Bottle
fields = '__all__'
My view (with form validation logic based on this answer):
def index(request):
args = {}
user = request.user
object = Bottle.objects.filter(brand__business__owner_id=user.id).all(). \
values('brand__name', 'name', 'capacity', 'vintage').annotate(Count('brand')).order_by('brand__count')
args['object'] = object
if request.method == "POST":
form = BottleForm(request.POST)
if form.is_valid():
bottle = form.save(commit=False)
bottle.save()
return redirect('index')
else:
form = BottleForm()
args['form'] = form
return render(request, template_name="index.pug", context=args)
And my template (in pug format), like so:
form(class="form-horizontal")(method="post" action=".")
| {% csrf_token %}
for field in da_form
div(class="form-group")
label(class="col-lg-3 col-md-3 col-sm-3 control-label") {{field.label_tag}}
div(class="col-lg-9 col-md-9 col-sm-9")
| {{ field|add_class:"form-control" }}
input(class="btn btn-primary")(type="submit" value="submit")
After a few hours of messing with my code and browsing SO, I managed to display the error by adding {{ form.errors }} to my template, but that only shows after the page has already been reloaded and in a very ugly form: see here.
What I'd like is to utilize django's built-in popover error messages without reloading page (see example on default non-empty field), which is so much better from a UX standpoint.
That is not a Django message. That is an HTML5 validation message, which is enforced directly by your browser. Django simply outputs the input field as type number with a max attribute:
<input type="number" name="capacity" max="750">
I'm not sure if your (horrible) pug templating thing is getting in the way, or whether it's just that Django doesn't pass on these arguments when you use validators. You may need to redefine the field in the form, specifying the max and min values:
class BottleForm(ModelForm):
capacity = forms.IntegerField(initial=750, max_value=2000, min_value=250)
(Note, doing {{ field.errors }} alongside each field gives a much better display than just doing {{ form.errors }} at the top, anyway.)

Django : Update ImageField from OneToOne relation

After several hours to try to update a ImageField in the views.py, I need your helps :
MODEL :
class ImageTeam(models.Model):
image = models.ImageField(upload_to="imageTeam/", null=False)
team = models.OneToOneField(Team,on_delete=models.CASCADE,related_name="theImage", null=False)
VIEW :
def update_team(request, idTeam):
try :
team = Team.objects.get(id = idTeam)
except Team.DoesNotExist :
return redirect(teams)
...
if request.method == "POST" :
form = updateTeamForm(request.POST, request.FILES)
if form.is_valid() and form.has_changed() :
team.name = form.cleaned_data["name"]
...
imageForm = form.cleaned_data["image"]
if imageForm :
if hasattr(team, 'theImage') :
team.theImage.image = imageForm
print(team.theImage.image) #Good it prints "<ImageFieldFile: imageName.jpg>"
team.theImage.save() #save doesn't works!
print(team.theImage.image) #Bad it prints "<ImageFieldFile: None>"
else :
#works!
ImageTeam.objects.create(image = imageForm, team=team)
...
TEMPLATE :
<form method="POST" enctype="multipart/form-data" action="{% url 'update_team' team.id %}" class="form-signin">
{% csrf_token %}
...
<div class="row">
{{ form.image }}
</div>
...
FORM :
class updateTeamForm(forms.ModelForm):
image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'id':'image_team'}))
...
class Meta :
model = Team
exclude = ['image',...]
I have tried many solutions (get the instance and save it, use request.FILES['image'], write directly in path...)
So why the imageField is not updated ?
I will very happy if I can fix this problem today
I think problem here is that imageForm = form.cleaned_data["name"]. ImageForm is not 'image'. If you want to store file, you need to get the file.
form.cleaned_data['image'] is what you need here. Also, I don't see any form field name so for sure the save is not going to work.
Also, good technique to debug these kinds of scenarios is do you a debugger like ipdb. Just put import ipdb; ipdb.set_trace() in the top of your function and step through the code during execution time.
Hope this helps!

Trouble with Django form validation

I have been pounding my head on this problem, been looking up Django docs on how to do form validation and can't seem to get any progress so I'm turning to SO for help.
I have a website with a Django form on which I display two ChoiceFields. One contains a list of car makes and the other contains a list of car models. The second field is initially blank and is populated with my Javascript when the user selects a car make. When the user selects both a make and model and clicks Submit, then a page will be loaded with info pertaining to the make/model that the user selected.
Now, if the user selects only a make but no model or no make at all, I want to display an error saying "Please select a make and model to continue."
So far, no matter what I've tried, I am unable to get that functionality.
forms.py
from django import form
makes = (('Honda', 'Honda'), ('Ford', 'Ford'), ('Nissan', 'Nissan'), ('GM', 'GM'))
class SearchForm:
make = forms.ChoiceField(choices=makes, required=True, widget=forms.Select(attrs={'size':'5', 'autofocus'='on'}))
model = forms.ChoiceField(required=True, widget=forms.Select(attrs={'size':'5'}))
def clean(self):
cleaned_data = super(SearchForm, self).clean()
m1 = cleaned_data.get("make")
m2 = cleaned_data.get("model")
if not m1 or not m2:
raise forms.ValidationError("Please select a make and model to continue.")
return cleaned_data
def clean_model(self):
data = self.cleaned_data["model"]
if not data: // if the user didn't select a model
raise forms.ValidationError("Please select a model to continue.")
return data
views.py
from mysite.forms import SearchForm
def search(request):
searchform = SearchForm()
return render(request, "search.html", {"form" : searchform})
def car_info(request):
searchform = SearchForm(request.GET)
if searchform.is_valid():
// code to parse the make and model and return the corresponding page
return render(request, "car_info.html", {})
else: // there is an error so redisplay the page with the new form object
return render(request, "search.html", {"form" : searchform})
search.html
...
<form action="{% url 'mysite.views.car_info' %}" method="GET" name="sform">
{{ searchform.make }}
{{ searchform.model }}
<button>Search away!</button>
</form>
{% if searchform.errors %}
<div class="error">{{ searchform.non_field_errors.as_text }}</div>
{% endif %}
...
In my index.html, I have a link to the search page by having: {% url 'mysite.views.search' %}
The behavior that I am getting is that when I don't select a make, I get the error. This is correct. When I do select a make but don't select a model, I also get the error. Good. But, when I do select a make and model, I get the same error and it will not take me to the car_info.html page.
Any help would be greatly appreciated.
EDIT:
Something weird is happening. I changed my forms.py to raise the Validation Error just to see what would pop out.
raise forms.ValidationError(str(clean_make) + " " + str(clean_model))
Then, I selected both make and model and the error was raised outputting the correct make for the make but "None" for the model, even though I had selected a model! Why is this?
EDIT 2:
Okay, I may know why the model is outputting "None". In my forms.py, I don't specify the choices for the model because that will be filled in with this Javascript code that runs on the search.html page:
$(document).ready(function() {
$("#id_make").change(init);
});
function init() {
populateModel();
}
function populateModel() {
var make = $("#id_make")
var model = $("#id_model") //select fields created by Django ChoiceField widget
var mod_values = values[mak.val()];
model.empty();
$.each(mod_values, function(k,v) {
model.append($("<option></option>").attr("value", v).text(k));
});
}
var values = {
"Honda" : {
"Accord" : "accord",
"Civic" : "civic",
"Odyssey" : "odyssey",
},
"Ford" : {
"F-150" : "f150",
"Taurus" : "taurus",
"Fusion" : "fusion",
},
"Nissan" : {
"Sentra" : "sentra",
"Maxima" : "maxima",
"Altima" : "altima",
},
}
So, when I do:
model = cleaned_data.get("version")
that'll be empty, right?
Now, I am now unsure how to fix it.
As a first debugging step, since you are using the GET method, you should be able to inspect the URL for correctness. When you submit the form with both a make and model selected, the URL should look like:
?make=XXXX&model=YYYY
Since it does, you are absolutely correct that the form is cleaning out the model because essentially it believes there are no valid entries for model. You'll need to create a custom field that validates the model:
class ModelField(forms.ChoiceField):
def valid_value(self, value):
if value is not None and value != '':
return True
return False
Then, in your form:
model = ModelField(required=True, widget=forms.Select(attrs={'size':'5'}))