I am using a custom MultipleChoiceField, in order to display user's full names instead of their logins.
I customized the way items are displayed using the choices attribute of MultipleChoiceField:
assignees = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()])
Users names are correctly displayed, but the problem is that on edition (i.e. where the Form is given an instance of Story with existing assigned users), the corresponding <option> are not selected...
I tried using the initial keyword but that didn't helped.
self.fields['assignees'] = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()],
initial=[user.id for user in self.instance.assignees])
models.py
class Story(BaseModel):
assignees = models.ManyToManyField(User,
blank=True,
related_name="assigned")
forms.py
class StoryForm(forms.ModelForm):
assignees = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()])
views.py
def story_edit(request, story_id):
if request.method == 'POST':
story = get_object_or_404(Story, id=story_id)
form = StoryForm(request.POST, instance=story)
if form.is_valid():
form.save()
else:
story = get_object_or_404(Story, id=story_id)
form = StoryForm(instance=story)
return render(request, 'story_form.html', {'form': form})
story_form.html
<p>
{{ form.assignees.errors }}
{{ form.assignees }}
</p>
Related
I'm building a page that allows users to edit Task and related Activity records (one task can have many activities), all on the same page. I want to allow the user to "adopt" one or more activities by ticking a box, and have their user record linked to each activity via a ForeignKey. Here are extracts from my code...
models.py
from django.contrib.auth.models import User
class Task(models.Model):
category = models.CharField(max_length=300)
description = models.CharField(max_length=300)
class Activity(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
notes = models.TextField(blank=True)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
The activity "owner" is linked to a User from the Django standard user model.
I added an extra field in the form definition for the adopt field - I don't want to add it to the model as I don't need to save it once it's done it's job.
forms.py
class ActivityForm(forms.ModelForm):
adopt = forms.BooleanField(required=False)
class Meta:
model = Activity
fields = '__all__'
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## ? DO SOMETHING HERE ? ##
formset.save()
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
I just can't figure out how to do that - if it was a single form (rather than an InlineFormSet), I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this). Or try save(commit = False) and update the model instance then save() it.
Maybe I have to iterate through the formset in the view code and try one of those options when I find one that had adopt=True?
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
formset = TaskInlineFormSet(request.POST, instance = task)
if formset.adopt:
# If True
formset.user = request.user
formset.save()
I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this).
You should give it a try.
I'm not happy with this solution but it does work. I iterate through the forms and change the object instance if my adopt field is set.
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## HERE'S WHAT I ADDED ##
for form in formset:
if form.cleaned_data['adopt'] is True:
form.instance.owner = request.user
## END OF ADDITIONS ##
formset.save()
## return redirect('manage_task',pk=task.id) # CHANGED THIS BECAUSE I WASN'T RETURNG ERRORS!
if not form.errors and not formset.total_error_count():
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
I wish I could find more in the docs about how the form saving works but I think I'll have to look into the code if I want more detail.
views.py
#login_required(login_url="/accounts/login/")
def add_project(request):
if request.method == 'POST':
form = forms.CreateProject(request.POST, request.FILES)
if form.is_valid():
# save in db
instance = form.save(commit=False)
instance.candidate = request.user
instance.save()
return redirect ('view_project')
else :
form = forms.CreateProject()
return render(request, 'home/add_project.html', {'form': form})
#login_required(login_url="/accounts/login/")
def view_project(request):
return render(request, 'home/view_project.html')
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class project(models.Model):
Name_of_the_organisation_or_Individual_applying = models.CharField(max_length=200)
Name_of_the_Project = models.CharField(max_length=200)
Name_of_the_Principal_Investigator = models.CharField(max_length=200)
date = models.DateTimeField(auto_now_add = True)
Cover_Letter = models.FileField(upload_to=None, max_length=254)
Summary_of_Project = models.CharField(max_length=500)
Study_Proposal = models.CharField(max_length=1000)
Any_other_documents_required = models.FileField(upload_to=None, max_length=254)
candidate = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
This is the views.py file where the project is added into the database for a particular user and the HTML file used for this is add_project.html
To display all the project, you have to pass them in your view using Django's context. Also, you need to use the query method on the models which you want to retrieve all of it's data. look into the code below to get a better idea.
#login_required(login_url="/accounts/login/")
def view_project(request):
projects = project.objects.all()
context = {"projects":projects}
return render(request, 'home/view_project.html', context)
Then in your html file, you can then loop through all the "projects" context like this:
{% for project in projects %}
<h1>{{project.Name_of_the_organisation_or_Individual_applying}} </h1>
<h1>{{project.Name_of_the_Project}} </h1>
# and so on for all other fields
{% endfor %}
I understand that in add_project view user fills in the form and upons submission gets redirected to view_project view, and in view_project you want to show list of all projects. For that you need to pass them in context to template, via third parameter, and then iterate over them in the template to render a list or table.
#login_required(login_url="/accounts/login/")
def view_project(request):
projects = project.objects.all()
return render(request, 'home/view_project.html', {'projects': projects})
I am a newbie to Django and could not find similar questions after searching on google/SO.
I've a model named Questions, which has multiple(2-4) choices and defined as below:
class Question(models.Model):
name = models.CharField(max_length=128)
class Choice(models.Model):
name = models.CharField(max_length=256)
is_correct = models.BooleanField(default=False)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Of the multiple choices only one is correct.
What I want to do: In just one page, user could submit a question together with multiple choices, here is a draft of UI:
My first question: I've defined ModelForm but don't know how to add "choices" field to QuestionForm:
class QuestionForm(ModelForm):
name = forms.CharField(max_length=128)
description = forms.CharField(max_length=256)
class Meta:
model = Question
fields = ['name', 'description']
class ChoiceForm(ModelForm):
name = forms.CharField(max_length=256)
is_correct = forms.BooleanField()
class Meta:
model = Choice
fields = ['name', 'is_correct']
Is it possible to use ModelForm the render the above HTML page besides writing it manually?
My second question: If use clicks "Submit" button, I use AJAX to send json data to backend server, here is an example of form data:
name:question1
choices[][name]:choice1
choices[][is_correct]:1
choices[][name]:choice2
choices[][is_correct]:0
And this is my code handling the request:
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save()
How to parse choices from the request?
How could I parse data of multiple choices part from the POST request?
Again, I'm a newbie to Django and any answers/suggestions is highly appreciated.
To create forms for models which have a OneToMany relation I would recommend you to use Django's inline formsets: https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#inline-formsets
It's a really simple and elegant way to create forms for related models.
To parse the choices, the user entered you could just override the clean method of your form. In this the user content is usually checked and prepared for storing it to the database. https://docs.djangoproject.com/en/1.8/ref/forms/validation/#form-field-default-cleaning
So cleaning could look like this:
class QuestionForm(ModelForm):
...
def clean(self):
cleaned_data = super(QuestionForm, self).clean()
if cleaned_data['choice_name'].startswith('Something'):
raise forms.ValidationError(
"Choice names cannot start with 'Something'!"
)
You models seems to be correct, in order to be able to add mutiple choices in your template you need a formset. In addition you can put a formset and a form inside the same html form in a template and have them be validated individually. Each one only cares about the POST data relevant to them. Something like:
template.html
<form method="post" action="">
{% csrf_token %}
{{ choices_formset.management_form }} <!-- used by django to manage formsets -->
{{ question_form.as_p }}
{% for form in choices_formset %}
{{ form.as_p }}
{% endfor %}
<button type='submit'>Submit</button>
</form>
views.py
from django.db import IntegrityError, transaction
from django.shortcuts import redirect
from django.forms.formsets import formset_factory
from django.core.urlresolvers import reverse
def new_question(request):
ChoicesFormset = formset_factory(ChoicesForm)
if request.method == 'POST':
question_form = QuestionForm(request.POST)
choices_formset = ChoicesFormset(request.POST)
if question_form.is_valid():
question = Question(**question_form.cleaned_data)
if choices_formset.is_valid():
question.save()
new_choice_list = list()
append_choice = new_choice_list.append
for form in choices_formset:
form.cleaned_data.update({'question': question})
append_choice(Choice(**form.cleaned_data))
try:
with transaction.atomic():
Choice.objects.bulk_create(new_choice_list)
except IntegrityError as e:
raise IntegrityError
return redirect(reverse('question-detail-view', kwargs={'id': question.id}))
def question_detail(request, id):
question_list = Question.objects.get(id=id)
return render(request, 'question_detail.html', {'question_list': question_list})
urls.py
url(r'^question/$', new_question, name='new-question-view'),
url(r'^question/(?P<id>\d+)/$', question_detail, name='question-detail-view'),
If you want to use rather Ajax submission rather than django form sumbission check this tutoriel.
I'm trying to create a web using django where a form is prompted to the user, that can fill some values of it and submit them. Then, the python program will fill the rest and show the form filled by the user, with also the fields filled by the server. I am using modelForms, as I want a complete matching between my model and my form.
For several reasons I have come to do it using the following code, but I don't know why after submitting the form, the fields don't appear as CharFields anymore, but as something similar to 'html labels', and are not editable anymore.
The code is a very simplified version of mine, where there is only one field, filled by the user.
views.py:
def manage_printers(request):
p = PrinterForm(request.POST or None)
if request.method == 'POST':
if p.is_valid():
f = p.save(commit=False)
f.name = request.POST.get('name')
f.save()
return render_to_response('web.html', {'printer': f})
else:
return HttpResponse("form not valid")
return render_to_response('web.html', {'printer': p}, )
models.py:
class Printer(models.Model):
name = models.CharField(max_length=200, default=' ')
def __unicode__(self):
return self.name
class PrinterForm(ModelForm):
class Meta:
model = Printer
web.html:
<form action="/useriface/" method="post">
<p>Name: {{ printer.name }}</p>
</form>
As I explained in the comment, when you save a form, you get a model instance. A model instance is not a form. It doesn't have form fields. When you pass the saved instance into the template in place of a form, you won't display form fields, you'll display the values of the saved instance's fields. If you want to redisplay the form, you should pass that into the template, not the saved instance.
i'm trying to write a very simple django app. I cant get it to show my form inside my template.
<form ...>
{{form.as_p}}
</form>
it shows absolutely nothing. If I add a submit button, it only shows that.
Do I have to declare a form object that inherits from forms.Form ? Cant it be done with ModelForms?
[UPDATE]Solved! (apologize for wasting your time)
In my urls file I had:
(r'login/$',direct_to_template, {'template':'register.html'}
Switched to:
(r'login/$','portal.views.register')
And yes, I feel terrible.
Background:
I have a Student model, and I have a registration page. When it is accessed, it should display a textfield asking for students name. If the student completes that field, then it saves it.
#models.py
class Student(models.Model):
name = models.CharField(max_length =50)
#forms.py
class StudentForm (forms.ModelForm):
class Meta:
model = Student
So, here is my view:
def register(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
form.save()
return render_to_response('/thanks/')
else:
student = Student()
form = StudentForm(instance =student)
return render_to_response('register.html',{'form':form})
The problem is in your view. You will have no existing student object to retrieve from the database. The following code sample will help you implement an "create" view.
As a side note, you might like using the direct_to_template generic view function to make your life a bit easier.
def add_student(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
new_student = form.save()
return HttpResponseRedirect('/back/to/somewhere/on/success/')
else:
form = StudentForm()
return direct_to_template(request,
'register.html',
{'form':form})