Django forms: object has no attribute cleaned data - django

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

Related

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

Add "duplicate" objects to a table in Django

I have a model called Song and another called Chord. They have a ManyToMany relationship through a label called ChordIndex.
A song might need to have the same chord more than once, which django would not allow. Hence, I gave the model an extra field (manually coded auto-incremental field) called index, so objects are not identical.
Still I can't add "duplicate" chords.
Nevertheless the approach works with a form and a POST method. But, if I use the POST method I am unable to set song field to default="lastSongByUser" because you can only query "user" inside a view.
models.py
class ChordIndex(models.Model):
def incrementalNumber():
objectCount = ChordIndex.objects.count()
if objectCount == None:
return 2
else:
return objectCount + 1
index = models.PositiveIntegerField(default=incrementalNumber)
chord = models.ForeignKey('Chord', on_delete=models.CASCADE)
song = models.ForeignKey('Song', on_delete=models.CASCADE)
views.py
def addchord_view(request, idChord):
user = request.user.profile
chord = Chord.objects.get(pk=idChord)
songsByUser = Song.objects.filter(uploader=user)
lastSongByUser = songsByUser.last()
previousPage = request.META.get('HTTP_REFERER')
filterUserLastSong = ChordIndex.objects.filter(song=lastSongByUser)
lastSongByUser.chords.add(chord)
thisObjectAdded = filterUserLastSong.last()
thisObjectAdded.save()
return HttpResponseRedirect(previousPage)
editchords.html
{% for chord in allChords %}
{% if chord.acronym != silent%}
<button class="btn btn-dark">{{ chord.acronym }}</button>
{% else %}
<button class="btn btn-primary"><a class="text-dark" href="{% url 'add-chord-view' idChord=chord.pk %}">next part</a></button>
{% endif %}
{% endfor %}
I would expect to get same results that from POST method.
You can add duplicate objects like this:
def addchord_view(request, idChord):
user = request.user.profile
chord Chord.objects.get(pk=idChord)
songsByUser = Song.objects.filter(uploader=user)
lastSongByUser = songsByUser.last()
previousPage = request.META.get('HTTP_REFERER')
filterUserLastSong = ChordIndex.objects.create(song=lastSongByUser, chord=chord)
return HttpResponseRedirect(previousPage)
Explanation
When you call ChordIndex.objects.create(song=lastSongByUser, chord=chord), it will create a new ChordIndex instance. Then if you call song.chords.all(), then you should get multiple chords for a song ordered by creation. You don't need to use index = models.PositiveIntegerField(default=incrementalNumber) field as well in your model, so you can remove that.

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.)

Cannot get selection from one page to another - need to know what choice user chose

I'm trying to let the user select one 'thing' from a list (from the database), then go find other stuff in the database using that record. But I cannot get the selection info from the selection page.
I'll try to make this a pretty complete snapshot of the relevant code, but I may remove too much or leave too much in, sorry.
my models.py:
urlpatterns = patterns('',
url(r'^$', 'dblook.views.index', name='home'),
url(r'^dblook3/', 'dblook.views.try3', name='home2'),
url(r'^dblook4/', 'dblook.views.try4', name='home3'),
)
my dblook/models.py:
from django.db import models
class serial_number(models.Model):
def __unicode__(self):
return self.serialno
#return self.question
class Meta:
managed=False
db_table='serial_number'
sn_id = models.AutoField(primary_key=True)
serialno = models.CharField(max_length=128)
comment = models.ForeignKey(comment,null=True,db_column='comment')
my views.py (I will skip all the imports other than the database model import. If anyone really wants them I'll update with them)
from dblook.models import *
class SerialnoSelectForm(forms.Form):
serialno = forms.CharField(max_length=16)
selected = forms.BooleanField()
class serialform(ModelForm):
class Meta:
model = serial_number
exclude=('comment','sn_id')
selected = forms.BooleanField()
class snselect(forms.Form):
sno = forms.ChoiceField()
def try3(request):
if ( request.POST ):
output = "HEllo world, thanks for posting"
return HttpResponse(output)
else:
sslst = snselect(serial_number.objects.filter(serialno__startswith="A128").order_by('-serialno'))
t = loader.get_template('select_serialno.html')
c = Context({
'sslst': sslst,
})
c.update(csrf(request))
return HttpResponse(t.render(c))
def try4(request,dsn):
if ( request.POST ):
output = "HEllo world, thanks for posting to 4"
return HttpResponse(output)
else:
return HttpResponse("Error")
And my template (select_serialno.html) is:
<h1>Select a serial number</h1>
<ul>
<form method='post' action'='/dbtest4/{{serial_number.sn_id}}/showme'>
{% csrf_token %}
{% for sn in sslst %}
<input type="submit" name="sn.serialno" id="choice{{ forloop.counter }}" value="{{choice.id}}"/>
<label for="choice{{ forloop.counter }}">{{ sn.serialno }}</label><br/>
{% endfor %}
<input type="submit" value="data" />
</form>
When I go to dblook3, I get a nice list from the database of serial numbers, along with a button that, if I hit goes immediately to the dblook4 URL (in this case, its ALWAYS '/dbtest4//showme/' instead of something like '/dbtest4/3/showme/). Unfortunately, I cannot seem to have any way to tell what button they hit.
No matter WHAT I put in for the 'stuff' in <form method='post' action'='/dbtest/{{stuff}}/showme'>, it is always empty.
I also tried things like if( 'choice' in request.POST ): in try4 in veiws.py, but that didn't work either.
So, how do I get ANY information about what was selected from 'look3' over to 'look4'? I'll take just about anything... However, if you can explain why I'm doing that hopefully your answer will not only solve my problem, but help others understand...
(if the above looks pretty 'evolutionary' that's because I've been hacking on this for 3 days now...)
Thanks!
You need to POST the information to the look4 dblook form:
<form method='post' action'='{% url dblook.views.try4 %}'>
At the moment you have /dbtest/{{serial_number.sn_id}}/showme which doesn't make any sense. You don't have a serial_number variable in your context so I don't know where that comes from. You have def try4(request,dsn): as your view definition which suggests that you are trying to load information on the try4 view depending on what was selected fromt he try3 view (although I am guessing this as you haven't explained what you are trying to do). If that is the case, you need to do that based on the data passed via POST instead of url parameters. Something very vaguely like the following:
def try4(request):
if request.method == "POST":
form = snselect(request.POST)
if form.is_valid():
data = form.cleaned_data
# Get the selected item from your choice field and retrieve the
# corresonding model object with that id
...

Model Forms in 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})