drewwyatt
drewwyatt

Reputation: 6027

Are Django inline formsets on initial submission of model forms possible?

I have seen plenty of similar posts, but nothing that had enough information to help me.

Disclaimer: I am very new to Django.

I am attempting to create an employment application for my company that will be hosted on a subdomain (e.g. jobs.mycompany.com) - this is my first real Django project. and what I am hoping to accomplish for the end-user has already been done in the admin section.

Basically, I broke the job application up into a few sections:

Here is the issue - I don't think I am putting this into one form correctly, and I also don't have a clue how to save all of these at one time. I have attempted to make form sets for Education and Job_Experience, but I don't think I am applying those correctly either.

Essentially, I want all of this to appear, and when the user clicks "submit" it creates all necessary records - applicant and availability being the only portions actually required.

Edit

To re-iterate: The admin panel is doing exactly what I want to achieve on the front end., but (on the front end) I have been unable to:

Code below:

models.py

from django.db import models
from django import forms
from django.forms import ModelForm
from datetime import datetime

class Applicant(models.Model):
    name = models.CharField(max_length=200)
    city = models.CharField(max_length=200)
    state = models.CharField(max_length=200)
    zip = models.CharField(max_length=200)
    social_security_number = models.CharField(max_length=200)
    phone = models.CharField(max_length=200)
    alt_phone = models.CharField(max_length=200, blank=True)
    us_citizen = models.BooleanField()
    committed_felony = models.BooleanField()
    is_16 = models.BooleanField()
    has_drivers_license = models.BooleanField()
    is_disabled = models.BooleanField()
    prev_employed = models.BooleanField()
    felony_explanation = models.TextField(blank=True)
    disabled_explanation = models.TextField(blank=True)
    prev_employment_manager = models.CharField(max_length=200, blank=True)
    prev_employment_year = models.CharField(max_length=4, blank=True)
    skills = models.TextField()

    def __unicode__(self):
        return self.name

class Education(models.Model):
    GED = 'GED'
    HIGH_SCHOOL = 'HIG'
    JUNIOR_COLLEGE = 'JUN'
    UNIVERSITY = 'UNI'
    TYPE_OF_SCHOOL_CHOICES = (
        (GED, 'GED'),
        (HIGH_SCHOOL, 'High School'),
        (JUNIOR_COLLEGE, 'Junior College'),
        (UNIVERSITY, 'University'),
    )

    type = models.CharField(
        max_length=3,
        choices=TYPE_OF_SCHOOL_CHOICES,
        default=HIGH_SCHOOL
    )
    school_name = models.CharField(max_length=200)
    school_city = models.CharField(max_length=200)
    school_state = models.CharField(max_length=200)
    graduated = models.BooleanField()
    graduation_year = models.CharField(max_length=4)
    applicant = models.ForeignKey(Applicant)

class Job_Experience(models.Model):
    FULL_TIME = 'F'
    PART_TIME = 'P'
    FTPT_CHOICES = (
        (FULL_TIME, 'Full Time'),
        (PART_TIME, 'Part Time'),
    )

    organization_name = models.CharField(max_length=200)
    organization_city = models.CharField(max_length=200)
    organization_state = models.CharField(max_length=200)
    supervisor_name = models.CharField(max_length=200)
    supervisor_phone = models.CharField(max_length=200)
    supervisor_contact_allowed = models.BooleanField()
    currently_employed = models.BooleanField()
    start_date = models.DateField()
    end_date = models.DateField()
    starting_title = models.CharField(max_length=200)
    ending_title = models.CharField(max_length=200)
    start_salary = models.CharField(max_length=20)
    end_salary = models.CharField(max_length=20)
    reason_for_leaving = models.TextField()
    full_time_part_time = models.CharField(
        max_length = 1,
        choices = FTPT_CHOICES,
        default = PART_TIME
    )
    applicant = models.ForeignKey(Applicant)

class Availability (models.Model):
    NOT_AVAILABLE = 'XX'
    OPEN_AVAILABILITY = 'OP'
    AVAILABLE_BETWEEN = 'AB'
    AVAILABILITY_CHOICES = (
        (NOT_AVAILABLE, 'Not Available'),
        (OPEN_AVAILABILITY, 'Available All Day'),
        (AVAILABLE_BETWEEN, 'Available Between Certain Hours'),
    )

    mon_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    mon_hours_start = models.CharField(max_length = 10)
    mon_hours_end = models.CharField(max_length = 10)
    tue_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    tue_hours_start = models.CharField(max_length = 10)
    tue_hours_end = models.CharField(max_length = 10)
    wed_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    wed_hours_start = models.CharField(max_length = 10)
    wed_hours_end = models.CharField(max_length = 10)
    thu_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    thu_hours_start = models.CharField(max_length = 10)
    thu_hours_end = models.CharField(max_length = 10)
    fri_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    fri_hours_start = models.CharField(max_length = 10)
    fri_hours_end = models.CharField(max_length = 10)
    fri_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    sat_hours_start = models.CharField(max_length = 10)
    sat_hours_end = models.CharField(max_length = 10)
    sat_availability = models.CharField(
        max_length = 2,
        choices = AVAILABILITY_CHOICES,
        default = NOT_AVAILABLE
    )
    sun_hours_start = models.CharField(max_length = 10)
    sun_hours_end = models.CharField(max_length = 10)
    applicant = models.OneToOneField(Applicant)

# Forms

class ApplicantForm(ModelForm):
    class Meta:
        model = Applicant

class EducationForm(ModelForm):
    class Meta:
        model = Education

class JobExperienceForm(ModelForm):
    class Meta:
        model = Job_Experience

class AvailabilityForm(ModelForm):
    class Meta:
        model = Availability

views.py

from django.shortcuts import render
from django.http import HttpResponse
from django.template import Context, loader
from applications.models import Applicant, Education, Job_Experience, Availability, ApplicantForm, EducationForm, JobExperienceForm, AvailabilityForm
from django.forms.formsets import formset_factory


def index(request):
    education_formset = formset_factory(EducationForm, extra=3)
    message = 'Forms have not been submitted.'

    if request.method == 'POST':
        applicant_form = ApplicantForm(request.POST)
        education_form = education_formset(request.POST)
        if applicant_form.is_valid() and education_form.is_valid():
            applicant_form.save()
            education_form.applicant = applicant_form
            message = 'Forms are valid.'
        else:
            message = 'Forms are not valid.'
    else:
        applicant_form = ApplicantForm()
        education_form = education_formset()

    return render(request, 
        'applications/index.html', 
        {
            'applicant_form' : applicant_form,
            'education_form' : education_form,
            'message' : message
        }
    )

admin.py

from django.contrib import admin
from applications.models import Applicant, Education, Job_Experience, Availability

class EducationInline(admin.StackedInline):
    model = Education
    extra = 3

class JobExperienceInline(admin.StackedInline):
    model = Job_Experience
    extra = 3

class AvailabilityInline(admin.StackedInline):
    model = Availability

class ApplicantAdmin(admin.ModelAdmin):
    inlines = [EducationInline, JobExperienceInline, AvailabilityInline]

admin.site.register(Applicant, ApplicantAdmin)

index.html

<h1>Employment Application</h1>
<p>Please enter your information into the fields below.</p>
<hr />
<p>{{ message }}</p>
<hr />
<form action="{% url 'applications:index' %}" method="post">
    {% csrf_token %}
    {{ applicant_form.as_p }}
    <hr />
    {{ education_form.as_p }}
    <input type="submit" />
</form>

Upvotes: 1

Views: 622

Answers (2)

Fiver
Fiver

Reputation: 10167

I realize this is a couple months old, and maybe you've solved this already using Django or are using something else now, but you were very close. The basic view pattern for your use case is to:

  1. Setup the formset
  2. Validate the parent form
  3. Validate the formset

Something like the following:

def index(request):

    EducationFormSet = formset_factory(
        Application,
        Education,
        form=EducationForm,
        extra=3,
    )

    if request.method == 'POST':
        application_form = ApplicationForm(request.POST)

        if application_form.is_valid():
            application = application_form.save(commit=False)
            education_formset = EducationFormSet(request.POST, instance=application)

            if education_formset.is_valid():
                application.save()
                education_formset.save()

                return HttpResponseRedirect(reverse('thanks_and_good_luck_view'))
        else:
            education_formset = EducationFormSet(request.POST)
    else:
        application_form = ApplicationForm()
        education_formset = EducationFormSet()

    return render_to_response(
        'applications/index.html',
        {
            'application_form': application_form,
            'education_formset': education_formset,
        },
        context_instance=RequestContext(request)
    )

The tricky bit here is the commit=False when saving the parent form. This allows you to get an uncommitted instance of the parent model to use for the child model instances in the formset.

Upvotes: 1

Burhan Khalid
Burhan Khalid

Reputation: 174614

As I understand your question, you want the same admin inline functionality on your front end.

The django admin application uses customized jquery for its front end. Part of that is the automatic generation of sub forms.

To get started with that on your own front end, start with the formsets section in the documentation. This will give you the basic idea on how multiple sub-forms work in the view. Then you can move to inline formsets which is what the admin uses to render its forms.

For the javascript part, you can use django-dynamic-formset or something more comprehensive like crispy forms which provides better rendering and support for dynamic inlines.

Upvotes: 0

Related Questions