Django forms: prepopulate form with request.user and url parameter - django

I'm building simple Course Management App. I want Users to sign up for Course. Here's sign up model:
class CourseMembers(models.Model):
student = models.ForeignKey(Student)
course = models.ForeignKey(Course)
def __unicode__(self):
return unicode(self.student)
Student model is extended User model - I'd like to fill the form with request.user.
In Course model most important is course_id, which i'm passing into view throught URL parameter (for example http://127.0.0.1:8000/courses/course/1/).
What i want to achieve, is to generate 'invisible' (so user can't change the inserted data) form with just input, but containing request.user and course_id parameter.

You want hidden inputs:
<input type="hidden" name="user" value="{{request.user}}"/>
I'd pass in the course_id as a context variable personally, not in the GET:
<input type="hidden" name="course_id" value="{{course_id}}"/>
Or you can get the value from the url string using {{request.get_full_path}}, and some slicing.

I've found answer to my own question. So here's step by step:
First we need to remove all the fields in ModelForm:
class AddCourseMemberForm(forms.ModelForm):
class Meta:
model = CourseMembers
fields = {}
As there's no data we want to get through user input, we just send empty POST request, and then insert the data directly to our form model, and then save it:
if request.method == 'POST':
form = AddCourseMemberForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.student_id = user.id #from request.user
form.course_id = course.id #from url parameter i.e courses/course/1/
form.save()
return redirect('index')
As I'm still learning I need to know if this is the safe way of doing things, as well as if is_valid method() makes sense. I think I need to clean course.id just in case and maybe validate right before save?

Related

How to pass primary key of one model into a form field of another model?

I have two models, Task and TaskNote, and I am trying to get the current Task's primary key entered as an initial value in the TaskNoteForm when the form is called from the current Task's view.
On the task detail view there is a link to a form where the user can write a note that will appear on the detail view. I would like the form to have the Task instance primary key set as a foreign key in TaskNote (for example, as model attribute "subject").
My solution was to get the URL using a 'HTTP_REFERER' request and parse the pk from the URL, then pass the pk into context data in the view, and finally enter it as a value in template. For example:
# Models
class Task(models.Model):
id = models.AutoField(
primary_key=True)
...
class TaskNote (models.Model):
...
subject = models.ForeignKey(
Task,
on_delete=models.CASCADE)
...
# view
def get_url_pk(self):
url = self.request.META.get('HTTP_REFERER')
t = url.split('/')
pk = int(t[-1])
return pk
class TaskNotesCreate(CreateView):
template_name='tasknotes_new.html'
form_class = TaskNoteForm
model = TaskNote
def get_context_data(self, **kwargs):
context = super(TaskNotesCreate, self).get_context_data(**kwargs)
context['pk'] = get_url_pk(self)
return context
def get_success_url(self, **kwargs):
obj = self.object
return reverse('task_view', kwargs={'pk': obj.subject_id})
# template
...
<div class="form-group">
<label for="exampleInput" class="required">
<input type="hidden" name="subject" value="{{ pk }}">
</div>
This works fine, however...
I have learned that HTTP_REFERER is not the preferred method as it can be disabled in some browsers. I can't seem to find the what the preferred method is in the docs, so if anyone could help me on that, that would be great. But also, before I spend a lot to time hacking something else together, I am wondering...
Is there a better, more acceptable way to pass the pk to the form???
Thanks
Rather than editing the template, you can do it the following way:
Update your TaskNoteForm so it has hidden input field for the value you want to pass along (this is a normal form field with widget=HiddenInput()).
On the view, overwrite get_initial to pass the object's value as the pk. This should look like
def get_initial():
initial = super().get_initial()
initial.update(
{"pk": self.object.pk}
)
return initial

Django - How to pass a form instance from one view to another

I have a form with multi steps in it. First part uses a forms.ModelForm, once completed, the user is re-directed to another view with a forms.Form in it.
Ideally, I would like to save the first form only if the secondary form is valid and save them both at the same time if that's the case.
I usually use self.request.session to pass data from one view to another, but a form's instance is not serializable, hence, can not append it to the session.
As an example:
FirstView contains FirstForm with fields ('firstname', 'lastname')
SecondView contains SecondForm with fields ('address', 'gender')
If FirstForm and SecondForm is valid
form1.save
form2.save
Would anyone have any suggestions?
Thank you.
Well, you can store form's cleaned_data in django session and pass it to another form as initial. For example:
def first_view(request):
form = FirstForm(request.POST)
if form.is_valid():
request.session['first_form'] = form.cleaned_data
return redirect('to_second_view')
def second_view(request):
form = SecondForm(request.POST)
if form.is_valid():
first_form_data = request.session.pop('first_form',{})
first_form_instance = FirstFormModel.objects.create(**first_form_data)
second_form_instance = form.save()
# rest of the code...

Django Forms choice-like field, without choice limitation

I am writing a writing a webapp that is basically just a form, and it has a button that duplicates a field so that multiple items can be entered. I can't use a SelectMultiple field or any of its variations because there is not a set number of choices to choose from. The user should be able to enter whatever they want into the fields and they must be saved in the model and linked to a record through a manytomany field. Here is a jsfiddle link for demonstration.
HTML
<form>
<label>Field 1
<textarea rows="3"></textarea>
</label>
<label>Multiple Values Possible</label>
<div>
<input type="text">
<select>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<button id="add_div">Add</button>
</form>
JS
add_div = document.getElementById("add_div");
add_div.onclick = function () {
var div = this.previousElementSibling;
var new_div = div.cloneNode(true);
this.parentNode.insertBefore(new_div, this);
return false;
}.bind(add_div);
I cannot figure out how to create the form backend for this. There aren't any field classes that can take in a variable amount of data and validate each one against another field.
What I have tried to do is create a MultiWidget/MultiValueField for the textbox/select dropdown pair, and then subclass my MultiValueField in a class closely following django's ModelMultipleChoiceField. I got stuck trying to get the form field to work with templates, allowing me to add all fields back to the rendered page when rendering with a particular form instance (like how when you use the CheckboxSelectMultiple widget, boxes that are checked in a form instance are rendered checked)
Is there any way to do this and have the ModelForm's save method also save the manytomany fields properly? I know I can override the form's save method and do something like in this stackoverflow question, but I would rather have all the save logic handled by the form fields themselves.
Based on looking at your example jsfiddle, it looks like you don't really need a "Choice Field", what you're looking for are Formsets.
In essence, you would have 2 forms on the page, one which is a normal form and would take care of Field 1, and one which is a Formset which deals with all the many-to-many relations for Field 2
Field2FormSet = formset_factory(Field2ToForm)
Make sure you output the management_form which you can clone with your "add" button.
What you are probably looking for is an inline formset, which can only be used if you are using django models (which you hinted at in your question).
Check out this guide: http://lab305.com/news/2012/jul/19/django-inline-formset-underscore/.
For the lazy, here is a quick example that gets you most of the way there. This app will allow you to continuously add Parent model objects to the database, along with any children that are filled out.
app/models.py
from django.db import models
class ParentModel(models.Model):
parent_field = models.CharField(choices=[(1, 1), (2, 2)])
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel)
child_field = models.IntegerField(choices=[(1, 1), (2, 2)])
app/views.py
from app import models
from django import forms
from django.forms.models import inlineformset_factory
from django.template import RequestContext, Template
from django.http import HttpResponse
class ParentForm(forms.ModelForm):
class Meta:
model = models.ParentModel
ChildFormSet = inlineformset_factory(models.ParentModel, models.ChildModel)
def test_view(request):
if request.method == "POST":
form = ParentForm(request.POST, request.FILES)
formset = ChildFormSet(request.POST, request.FILES, form.instance)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
else:
pass # Handle validation error
template = Template(
"<form action='<url for view>' method='post'>"
"{% csrf_token %}"
"{{ form.as_p }}"
"<p>{{ formset.as_table }}</p>"
"<input type='submit' value='Submit'/>"
"</form>"
)
context = RequestContext(request, {
"form": ParentForm(),
"formset": ChildFormSet(),
})
return HttpResponse(template.render(context))
What is shown above will only allow you add up to three children (the default number of extra forms the inline form set produces). To add dynamically, you are going to have to add some java script that creates new forms in the form set on the client side. For that, I suggest you look at the guide I posted above since I don't think I can do better job of explaining it.
Thanks to #Kevin and #Thomas for pointing me towards formsets! Here is how I did it:
models.py
from django.db import models
class RelatedField(models.Model):
field1 = models.CharField(max_length=50)
field2 = models.IntegerField(choices=[(x, x) for x in xrange(1, 11)])
class Record(models.Model):
user = models.ForeignKey(User)
field = models.CharField(max_length=20)
relatedA = models.ManyToManyField(RelatedField, related_name='relatedA')
relatedB = models.ManyToManyField(RelatedField, related_name='relatedB')
views.py
def getIndexContext(data):
if data is None:
recordForm = RecordForm()
relatedFormA = RelatedFormSet(queryset=RelatedField.objects.none(), prefix='related-a')
relatedFormB = RelatedFormSet(queryset=RelatedField.objects.none(), prefix='related-b')
else:
recordForm = RecordForm(data)
relatedFormA = RelatedFormSet(data, prefix='related-a')
relatedFormB = RelatedFormSet(data, prefix='related-b')
return {
'form': recordForm,
'relatedA': relatedFormA,
'relatedB': relatedFormB,
'title': 'Index',
}
def index(request):
if request.method == 'GET':
return render(request, 'record/index.html', getIndexContext(None))
else:
context = getIndexContext(request.POST)
form = context['form']
relatedA = context['relatedA']
relatedB = context['relatedB']
if form.is_valid() and relatedA.is_valid() and relatedB.is_valid():
obj = form.save(commit=False)
obj.user_id = request.user
obj.save()
form.save_m2m()
instances = relatedA.save()
obj.relatedA.add(*instances)
instances = relatedB.save()
obj.relatedB.add(*instances)
return HttpResponse('success!')
else:
return render(request, 'record/index.html', context)
And then some javascript that can duplicate the fields in the formsets, and increment the names by one.

django adding fields to model form

Following is the model which I have
class OrgStaff(BaseModel):
user = models.OneToOneField(User)
member_type = models.BooleanField(help_text="1. Read/Write 0. Read Only")
task = models.ForeignKey(ToDos, null=True, blank=True)
org = models.ForeignKey(Org)
# TODO Add possible activities
def __unicode__(self):
return self.user.username
Following is the forms file
class AddStaffForm(ModelForm):
class Meta:
model = OrgStaff
exclude = (
'task',
'org'
)
and this is how I process the view
if request.POST and form.is_valid():
form.save()
ret_url = reverse("some_view", kwargs={
'var':var,})
return redirect(ret_url)
return render(request, "form.html", {"form":form})
This form would render a dropdown, which will show all the users in the database, and a radio box.
But actually, I want to create the form, so that I can add a new user(first name, last name, username, email and password) and then the rest of the fields from the abvoe AddStaffForm.
So question basically boils down to adding fields of userform to the addstaffform.
And then handling them into the views.
Is it doable, or will I have to do it manually?
Can the above model form be extended so that I can first fill in a user detail, and then assign a type to him??
Please let me know, thanks.
Use two separate forms - UserForm, created out of models.User & AddStaffForm but exclude the 'user' field in the AddStaffForm. Use only ONE submit button for both.
So your template will look like:
<form method="post" action="/path/to/wherever">
{{ user_form }}
{{ add_staff_form }}
<button>Submit</button>
</form>
Then, when the user submits the form, process each form independently in the following order:
Process the user form first and save the user instance created by the form to the db. if user_form.is_valid() is True, you can do this by simply doing user = user_form.save()
Next, process the AddStaffForm but pass commit=False (i.e. staff = add_staff_form.save(commit=False) since it does not contain the value for the user field just yet. Provide the user values using staff.user = user and then staff.save()
Provided all other fields in the staff form are provided for (i.e. add_staff_form.is_valid() is otherwise True, this should result in the creation of a new staff instance written to db.
Hope this helps. :)

django inline editing - inline form only required if at least one field is filled out

I created a view which returns a form including a contact form and two phone_number forms, following this example:
multiple forms
The phone number forms should only be validated if the user inserts at least a value for one field in a phone number form. For example: a phone number has a type and a number. If the user is selecting the type, the number is required.
Now I'm wondering how i can check in the view whether the user inserted a value / selected a type or inserted a number. It should work like in the admin for inline editing a model.
my view looks like this:
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
pforms = [PhoneNumberForm(request.POST, prefix=str(x)) for x in range(0,3)]
if cform.is_valid() and all([pf.is_valid() for pf in pforms]):
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
for pf in pforms:
new_phone_number = pf.save(commit=False)
new_phone_number.contact = new_contact
new_phone_number.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
pforms = [PhoneNumberForm(prefix=str(x)) for x in range(0,3)]
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'pforms': pforms,},
context_instance = RequestContext(request),
)
Edit after first response below:
I tried to accomplish this task with custom validation but did not come to a satisfying end. To ease my task I changed the use-case a bit. I create a form which includes one Contact Form and one Address Form. The Address Form should only be validated if at least one field of the Address Form is filled in, since it should be possible to create a contact without creating a corresponding Address.
First I tried to use custome validation, which looked like this:
class AddressForm(forms.ModelForm):
class Meta:
model = Address
exclude = ('contact',)
def clean(self):
cleaned_data = self.cleaned_data
street = cleaned_data.get("street")
postal_code = cleaned_data.get("postal_code")
city = cleaned_data.get("city")
country = cleaned_data.get("country")
if not street and not postal_code and not city and not country:
#searching a better idea here
return 0
else:
return cleaned_data
But this does not really help, since this way I do not get rid of the validation errors.
This lead me to the idea that the clean method is the wrong place to do this validation, I think I have to check already in the POST.request whether all values for the Address Form are missing. And if they are missing, I do not call is_valid() for the Address Form and just ignore it. If at least one value is available, I just do the normal validation of the Address Form, without overriding the clean() method..
Good or bad idea?
If it is a good idea, how can I easily check the POST request for the values of my Address Form.
Probably I`m thinking way to complicated :-)
Edit: The solution using FormSets:
#login_required
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
phonenumberformset = PhoneNumberFormSet(request.POST)
if cform.is_valid() and classificationformset.is_valid() and addressformset.is_valid() and phonenumberformset.is_valid():
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
new_phonenumber_instances = phonenumberformset.save(commit=False)
for new_phonenumber in new_phonenumber_instances:
new_phonenumber.contact = new_contact
new_phonenumber.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
#By default, when you create a formset from a model, the formset will use
#a queryset that includes all objects in the model (e.g., Author.objects.all()).
#Here we want to present an empty formset in order to add a new object
phonenumberformset = PhoneNumberFormSet(queryset=PhoneNumber.objects.none())
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'phonenumberformset': phonenumberformset,},
context_instance = RequestContext(request),
)
Please note that this can also be accomplished using an inlineformset_factory, see my other post for more details: link
Note that if you are using FormSets you have to include a management_form for each form_set in your template. docs
Otherwise you get this error:
[u'ManagementForm data is missing or has been tampered with']
Using a formset inside a view is as easy as using a regular Form class. The only thing you will want to be aware of is making sure to use the management form inside the template.
{{ context.phonenumberformset.management_form }}
You should be using formsets rather than messing around with dynamic prefixes for your PhoneNumber subform - it will make everything much easier, and this is indeed how the admin manages inline forms (see also the model formsets documentation).
Formsets are intelligent enough that if no information is entered in one form of the formset, it does not enforce the required elements - but if one element is filled, it will enforce all the validation requirements. This sounds like it should solve your problem.
What you want to do is define custom validation on the form.
class PhoneNumberForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = self.cleaned_data
phone1 = cleaned_data.get("phone1")
if phone1:
# validate manually, and if it doesn't pass:
self._errors["phone1"] = ErrorList(["Hey, this field is wrong."])
del cleaned_data["phone1"]
# Always return the full collection of cleaned data.
return cleaned_data
Then in the view, you want to rely on Django's built-in error form validation error handling:
{{ pforms.phone1 }}
{{ pforms.phone1.errors }}