Long post ahead!
This is a follow-up to my previous question, which was about creating a feedback page that associates one form to each user. I was able to get that done, but it was done in what I guess is a hacked-together way because I'm seeing some really strange behaviour after I submit feedback one time.
Let's say I've not submitted any feedback previously, and now I want to submit feedback for two of three people (img). The hidden management_form details appear as follows:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
On submit this successfully creates two new entries in the Feedback table, as wanted. The problem is that now if I go to any feedback page (as any user) I see the feedback I have already created and I'll get errors on submit.
If I now go to a page with one or two users I'll see one or two forms (this is good) but the management_form data is incorrect. For example, on a one user page I'll see this management_form data:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
And get this error:
MultiValueDictKeyError at /feedback/2/
"u'form-1-id'"
Since I'm only supposed to have one form appear that's all I see, and it's set to the first of the two feedbacks already created, but there's obviously a problem with the management data. Initial should be 0 (not 2) and total forms should be 1 (not 3).
If I go to a page with an equal number of users I'll see the original three feedback forms, with this management_form data:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="5" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
Again, total should be 3 (not 5) and initial should be 0 (not 2). This time I don't get an error though, because I'll get a message saying that I need to fill in values for two forms which don't even appear on the page.
Hopefully that explains the problem well enough, so here's the code:
models.py
class Feedback(models.Model):
action = models.ForeignKey(Action)
feedback = models.CharField(max_length=1)
feedback_by = models.ForeignKey(UserProfile, related_name='feedback_by')
feedback_for = models.ForeignKey(UserProfile, related_name='feedback_for')
comment = models.CharField(max_length=200)
created = models.DateTimeField()
modified = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
return super(Feedback, self).save(*args, **kwargs)
forms.py
class FeedbackForm(forms.ModelForm):
choices = (('g', '(+1) Positive'),
('b', '(±0) Negative'),
('n', '(-1) No Show'),
('d', 'Don\'t Leave Feedback'))
feedback = forms.ChoiceField(widget=forms.RadioSelect(), choices=choices, initial='d')
comment = forms.CharField(widget=forms.Textarea())
class Meta:
model = Feedback
fields = ['feedback_for','feedback','comment']
views.py
#login_required
def new_feedback(request, action_id):
action = get_object_or_404(Action, id=action_id)
profile = UserProfile.objects.get(user_id=request.user.id)
participants = all_info_many_profiles(action.participants.filter(~Q(id=profile.id)))
fbformset = modelformset_factory(Feedback, form=FeedbackForm, extra=len(participants))
if request.method == 'POST':
formset = fbformset(request.POST, request.FILES)
if formset.is_valid():
#formset.save(commit=False)
for form in formset:
tmp = form.save(commit=False)
tmp.action = action
tmp.feedback_by = profile
if tmp.feedback != 'd':
tmp.save()
return index(request)
else:
print formset.errors
#return index(request)
else:
formset = fbformset()
return render(request, 'app/new_feedback.html',
{'action': action, 'participants': participants, 'formset': formset}
)
feedback.html
{% load multifor %}
{% block body_block %}
<h1>Leave Feedback</h1>
<form method="post" action="{% url 'app:new_feedback' action.id%}">
{% csrf_token %}
{% comment %}
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="{{participants.count}}" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
{% endcomment %}
{{ formset.management_form }}
{{ formset.errors }}
{% for form in formset; participant in participants %}
{{ form.id }}
{{ form.errors }}
<input id="id_form-{{forloop.counter0}}-feedback_for" name="form-{{forloop.counter0}}-feedback_for" type="hidden" value="{{participant.id}}" /> <br />
{{ form.feedback_for.label }} {{ participant.username }}: <br />
{% for radio in form.feedback %}
{{ radio }} <br />
{% endfor %}<br />
{{ form.comment.label }} {{ form.comment }} <br /><br />
{% endfor %}
<input type="submit" name="submit" value="Submit Feedback" />
</form>
{% endblock %}
The problem is that you are using a modelformset_factory, this factory is tied to the model and it will "help" you by populating stuff, this is why the count is at 5.
You might get the result you want by using a plain formset_factory. Django's docs has some examples
Related
I am trying to create a number of surveys in Django and put them in a survey index page. I have run into an issue: Up until now, all of my surveys had questions which had the same set of answer choices, so in my Django models I linked the answers only to the survey because it was the easiest for me and it was working. Now I have to add a new survey where each question has a different set of answers choices (if you are curious, this is the survey: https://www.fresno.ucsf.edu/pediatrics/downloads/edinburghscale.pdf). My problem is that for this new survey I am now getting every possible answer choice in the whole survey for each question.
How can I link the answer choices to the question so that I will only see the choices for the corresponding question? Yes, I have read the Django polls tutorial and have tried requesting the question_id, but then I got an error along the lines of "missing question_id" so I went back to my original code. I think the right approach would be to link the answer choices to both the question and the survey, but not sure.
Here is my model:
class Questionnaire(models.Model):
name = models.CharField(max_length=255)
text = models.CharField(max_length=200)
# def get_absolute_url(self):
# return reverse('questionnaire-detail', args=[str(self.id)])
def __str__(self):
return self.name
class Question(models.Model):
text = models.CharField(max_length=200)
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, related_name='questions')
def __str__(self):
return self.text
class Answer(models.Model):
question = models.ForeignKey(Question, models.SET_NULL, blank=True, null=True, related_name='answerlist')
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, related_name='answers', default='')
text = models.CharField(max_length=200)
image = models.CharField(null=True, max_length=20)
image_location = models.CharField(null=True, max_length=20)
def __str__(self):
return self.text
Here is my views:
ResponseFormSet = modelformset_factory(Response, form=ResponseForm, extra=0)
if request.method == 'POST':
formset = ResponseFormSet(request.POST or None, request.FILES, form_kwargs={'user': request.user})
if formset.is_valid():
print("Formset is valid")
formset.save()
return HttpResponseRedirect(reverse ('questionnaires:questionnaire_index'))
else:
print("Formset is NOT valid")
print(formset.errors)
print(formset.non_form_errors())
questionnaire = Questionnaire.objects.get(id=questionnaire_id)
questions = Question.objects.filter(questionnaire_id=questionnaire_id)
answers = Answer.objects.filter(questionnaire_id=questionnaire_id)
return render(request, 'questionnaires/questionnaire_lec.html', {
'questions': questions,
'answers': answers,
'questionnaire': questionnaire,
})
And my HTML:
<form method="post" action="{{ request.url }}">
{% csrf_token %}
<legend><h2>{{ questionnaire.text }}</h2></legend>
<br>
{% for question in questions %}
<h3>{{question.text}}</h3>
<label hidden="" for="id_form-{{ forloop.counter0 }}-question">Question:</label>
<select hidden="" name="form-{{ forloop.counter0 }}-question" id="id_form-{{ forloop.counter0 }}-question">
<option value="{{question.id}}" selected="">{{question.text}}</option>
</select>
<!-- <label for="id_form-{{ forloop.counter0 }}-answer">Answer:</label> -->
{% for answer in answers %}
<label for="id_form-{{ forloop.counter0 }}-answer">
<input type="radio" name="form-{{ forloop.parentloop.counter0 }}-answer" id="id_form-{{ forloop.counter0 }}-answer" value="{{answer.id}}">
{{answer.text}}
</label> <br>
{% endfor %}
<input type="hidden" name="form-{{ forloop.counter0 }}-id" value="{{ forloop.counter }}" id="id_form-{{ forloop.counter0 }}-id">
<input type="hidden" name="form-TOTAL_FORMS" value="{{questions|length}}" id="id_form-TOTAL_FORMS" />
<input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
<input type="hidden" name="form-MAX_NUM_FORMS" value="{{questions|length}}" id="id_form-MAX_NUM_FORMS" />
{% endfor %}
<br />
<br />
<input type="submit" value="Submit">
</form>
{% endif %}
{% endblock %}
You should use:
{% for answer in question.answerlist.all %}
…
{% endfor %}
There is no need to pass a QuerySet of Answers to the template.
my django template is not submitting data! i know it is very basic thing but there is something i cannot realize here! my Model is:
class project(models.Model):
Project_Name = models.CharField(max_length=50)
And my ModelForm is:
class create_project(forms.ModelForm):
class Meta:
model = project
fields = ['Project_Name']
views.py
def project_create_view(request):
form = create_project(request.POST or None)
msg = ''
if form.is_valid():
form.save()
msg = 'Data Submitted'
form = create_project()
return render(request, 'create_project.html', {'form':form, 'msg':msg})
And my template is:
<form action="" method="POST">
{% csrf_token %}
<table border="1">
<tr>
<td>
<div>
<label for="id_Project_Name">Project Name</label>
<input type="text" name="Project_Name" id="id_Project_Name">
</div>
</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
My context dict is 'form', i tried so many ways searched online but no luck, can anyone help?...
I haven't pasted all the project as the case is similar for the rest fields.
In your html,
<form method="POST" action="#keep it blank or add action as per your requirement" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<span> {{field.errors}} </span>
<div> {{field}} </div> #you can add label or placeholder as per your requirement
{% endfor %}
<input type="submit" value="Submit">
</form>
You have not added enctype in your html.
Note : You can modify your views.
i managed to solve it this way after taking support from one of my experienced friends:
<td>
<div>
<label for="{{form.Project_Name.name}}">Project Name</label>
<input type="text" name="Project_Name" id="{{form.Project_Name.name}}">
</div>
</td>
Is it possible to create a form that modifies when information is already find in the database or so adds information if there is nothing in the database ?
So I have a template that allows me to display the value if there is one in the base given to the fields or answer then display a blank field for the answer.
My template for my formset :
<form method="POST" action="">
{{ formset.management_form }} {% csrf_token %}
<table>
<!-- <br>{{ formset.as_table }}<br> -->
{% for question in questions %}<hr>
<label for="question">{{ question }} [{{ question.id }}]</label>
<input type="hidden" id="id_form-{{ forloop.counter0 }}-question" name="form-{{ forloop.counter0 }}-question" value="{{ question.id }}"/>
</p>
{% if question.reply_set.all %}
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
</p>
{% endfor %}
{% else %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.counter0 }}-answer" name="form-{{ forloop.counter0 }}-answer" placeholder="answer here"/>
</p>
{% endif %}
{% endfor %}
<br><br><br><hr> <br><br><br>
</table><br>
<center><input type="submit" value="Submit" class="btn btn-success" />
Retour</center>
</form>
My problem is that I get a good add when there is nothing in the database but when I have Data if I change its value this create another answer as I would like it replaced the answer with the new!
My views.py
def access(request, instance):
questions = Question.objects.filter(page=instance)
pages = Page.objects.all()
numPages = Page.objects.get(pk=instance)
length_questions = len(questions)
logged_user = get_logged_user_from_request(request)
instance = {'instance':instance}
ReplyFormSet = modelformset_factory(model=Reply, form=ReplyForm, extra=length_questions)
ReplyFormSet.form = staticmethod(curry(ReplyForm, instance=numPages))
if logged_user:
if request.method == 'POST':
formset = ReplyFormSet(request.POST, request.FILES)
if formset.is_valid():
new_instances = formset.save(commit=False)
for new_instance in new_instances:
new_instance.user = logged_user
new_instance.save()
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
messages.add_message(request, messages.INFO, "L'ajout à bien été effectué !")
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
formset = ReplyFormSet(queryset = Reply.objects.none())
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
return HttpResponseRedirect('/login/')
My model allows me to answer several questions at the same time ..
My models.py
class Page(models.Model):
title = models.CharField(max_length=30)
def __str__(self):
return self.title
class Question(models.Model):
label = models.CharField(max_length=30)
page = models.ManyToManyField(Page)
def __str__(self):
return self.label
class Reply(models.Model):
question = models.ForeignKey(Question)
user = models.ForeignKey(Personne)
answer = models.CharField(max_length=30)
creationDate = models.DateTimeField(default=django.utils.timezone.now)
def __str__(self):
return str(self.answer)
How can I change the values instead adding in once again when there is already in the database?
Edit : screenshot of the problem
This saves 2 other lines more when I submit ..
Although you can edit current replies using your current approach by adding a hidden id field in your reply forms like
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
<input type="hidden" id="id_form-{{ forloop.parentloop.counter0 }}-id" name="form-{{ forloop.parentloop.counter0 }}-id" value="{{ reply.id }}"/>
</p>
{% endfor %}
this will tell your ReplyFormset that these records already exist, so instead of creating new records, edit existing records.
Yet, most of the work you are doing manually, can be done using Django's built in functionality, i.e. Django Formsets (which you are using but not using right). Django Inline Formsets are the Django way of doing it.
Django form will create a new entry in db if you are calling a form without an instance.
form = MyForm()
On the contrary, if you want to update the existing entry in tha db pass the instance to the form.
form = MyForm(instance=myinstance)
I am trying to create an app where a user can manage a database of "Lost property" items. To do so I have a main page where all the items are displayed and I have a button per row to be clicked when the item is returned to the owner.
That button is submitting a form that should contain the ID value of the element that has been clicked so I trying to get something like
<input id="id_id" name="id" type="hidden" value="{{lostitem.id}}">
But I don't know how to pass that value to my form ! Here is my template :
{% for lostitem in lostitems %}
<tr>
<td>{{lostitem.id}}</td>
<td>{{lostitem.description}}</td>
<td>
<form class="give-back-item-form" method="POST">
{% csrf_token %}
{{formGiveBackItem.as_p}}
<button type="submit" class="button btn btn-xs btn-success buttonItems">
<span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span>
</button>
<!-- TRYING TO ADD A HIDDEN INPUT WITH THE ID AS VALUE -->
</form>
</td>
</tr>
{% endfor %}
Here is my form from forms.py
class GiveBackItemForm(forms.ModelForm):
id = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = ItemLost
fields = ('id',)
And here is where I'm trying to get my $_POST['id'] and to update my object (I couldn't test this part as I'm not getting any POST information at the moment) :
from .forms import GiveBackItemForm
"""Defining our views"""
def item_list(request):
formGiveBackItem = GiveBackItemForm()
"""Ordering objects by date of creation"""
lostitems = ItemLost.objects.filter(added_date__lte=timezone.now()).order_by('added_date')
if request.method == "POST":
"""Giving back an item"""
itemToGive = ItemLost.objects.get(pk=request.POST.get('id'))
itemToGive.giveBackItem
"""Returning our ordered objects to the view"""
"""Request = everything we receive from the user (in a form for example)"""
return render(request, 'lostitem/item_list.html', {'lostitems': lostitems, 'formGiveBackItem' : formGiveBackItem})
Thanks for any help or remark about the code ! I'm just getting started and it was really hard to find anything helpful about my problem
EDIT : I managed to make it work by still using the Django ModelForm and the view to handle my form
Here is my code in my view :
def item_list(request):
"""Ordering objects by date of creation"""
lostitems = ItemLost.objects.filter(added_date__lte=timezone.now()).order_by('added_date')
"""To get data from the form"""
give_back_item_form = GiveBackItemForm(request.POST or None)
# check if form is valid
if give_back_item_form.is_valid():
itemToGive = ItemLost.objects.get(pk=give_back_item_form.cleaned_data['id'])
itemToGive.returned_date=timezone.now()
itemToGive.save()
# your rest of the code here
"""Returning our ordered objects to the view"""
"""Request = everything we receive from the user (in a form for example)"""
return render(request, 'lostitem/item_list.html', {'lostitems': lostitems, 'give_back_item_form' : give_back_item_form})
And here is the code for my template !
<form class="give-back-item-form" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{ lostitem.id }}">
<button type="submit" class="button btn btn-xs btn-success buttonItems">
<span class="glyphicon glyphicon-share-alt" aria-hidden="true"> </span>
</button>
</form>
Thank you all for your answers it lead me to the right solution !
If all you want to do is post back the id of an associated ItemLost object so that you can invoke a method on it (e.g., giveBackItem()), there's no need to use a ModelForm at all. Just use a normal HTML <form>, and manually put the hidden field in it:
<form class="give-back-item-form" method="POST">
{% csrf_token %}
<button type="submit" value="Give Back">
<input type="hidden" name="id" value="{{lostitem.id}}">
</form>
So your complete template would become:
{% for lostitem in lostitems %}
<tr>
<td>{{lostitem.id}}</td>
<td>{{lostitem.description}}</td>
<td>
<form class="give-back-item-form" method="POST">
{% csrf_token %}
<button type="submit" class="button btn btn-xs btn-success buttonItems">
<span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span>
</button>
<input type="hidden" name="id" value="{{lostitem.id}}">
</form>
</td>
</tr>
{% endfor %}
I have this html form:
<form method="post" action="">
{% csrf_token %}
<div class="inputText">
<input type="text" name="username" placeholder="Username" value = required />
<br><br>
<input type="email" name="email" placeholder="Email" required />
<br><br>
<input type="password" name="pass1" id="pass1" placeholder="Password" required />
<br><br>
<input type="password" name="pass2" id="pass2" onkeyup="checkPass(); return false;" placeholder="Confirm Password" required />
<br><br>
<span id="confirmMessage" class="confirmMessage"></span>
</div>
<div class="send">
<input type="submit" name="register" value="Register" class="register" />
</div>
This is my forms.py :
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'email', 'password')
And my view
def register(request):
if request.method == "POST":
form = UserForm(request.POST)
if form.is_valid():
form.save
new_user = authenticate(username=request.POST['username'], password =request.POST['password'])
login(request.new_user)
return HttpResponseRedirect(request, '/index')
else:
form = UserForm()
return render(request, 'authen/register.html', {'form': form})
This should be a register system but when I press register it reloads the register page and nothing. When I go to django admin I see no new user so django is not taking input from form fields.
The form is presumably not valid, but you are not displaying any errors in your template. At least do {{ form.errors }} somewhere, although really you should output all the fields and their errors from the Django form directly:
{{ form.username }}
{{ form.username.errors }}
etc.
Also note you are not actually invoking the save method in your is_valid block. You need to do:
form.save()
Be sure to get the input element by its name attribute. This is because request.POST['keyword'] refers to the element identified by the specified html name attribute keyword.
Here's an example:
<form action="/login/" method="POST">
<input type="text" name="keyword" placeholder="Search query">
<input type="number" name="results" placeholder="Number of results">
</form>
In your Python file, where you get the value of the input elements, request.POST['keyword'] and request.POST['results'] will contain the values of keyword and results, respectively.