Search code examples
pythondjangomany-to-manyformsdjango-validation

Django ManyToMany Validation and Save


I am developing a Leave of Absence Request Form for a client. The form which is filled out by an employee allows the employee to select mutiple days which he/she wants to take off. When the employee initially views the form only one date entry is shown. They can add more dates dynamically (javascript) by click an add button.

Database setup: Form_Table, Date_Table, Form_Date_ManyToMany Table (generated by Django)

I have 2 issues with Django.

1) If the employee enters 3 dates, each date field will have the same name. How can I validate each field using Django's Form or ModelForm? Here is an example of the the date field.

<input name="dates[]" />
<!-- or -->
<input name="dates" /> <!-- I have read this is the Django way -->

2) In my database I have a ManyToMany relationship with the Form and the Dates. I am sure I need ManyToMany and not simply a ForeignKey for tracking changes (this is not the question). How can I save multiple dates (in the dates table) and have the ManyToMany relationship with the form (in the forms table) using Django.?

I am relatively new to Python & Django. I switched from PHP since Django is the best framework I have found.

Edit: I guess I did not ask the correct question. Here is more information.

# Model
class Date(models.Model):
    date = models.DateField()
    emp_requested = models.ForeignKey(EmployeeRequested,null=True)
    hr_approved = models.ForeignKey(HumanResourcesApproved,null=True)

class Form(models.Model):
    pay_period = models.ForeignKey(PayPeriod)
    employee = models.ForeignKey(Employee)
    ack_ip = models.CharField(max_length=15, default='') 
    emp_signature = models.CharField(max_length=100)
    date_of_hire = models.DateField()
    state = models.ForeignKey(State)
    scheduler = models.ForeignKey(Scheduler)
    scheduler_form = models.ManyToManyField(SchedulerForm)
    hr_status = models.CharField(max_length=100, default='')
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    enabled = models.BooleanField(default=True)
    parent = models.ForeignKey('self',related_name='+')
    root  = models.ForeignKey('self',related_name='+')
    dates = models.ManyToManyField(Date)

# View - this is an ajax call 
def ajax_save_form(request):
    form = EmployeeForm(request.POST or None)
    return HttpResponse(form.is_valid())

# Form
class EmployeeForm(forms.ModelForm):
    employee_name = forms.CharField(max_length=30)
    employee_email = forms.EmailField()
    county = forms.ModelChoiceField(queryset=County.objects.all())
    job_type = forms.ModelChoiceField(queryset=JobType.objects.all())
    pay_period = forms.CharField()
    total_hours = forms.DecimalField()

    class Meta:
            model = Form
            exclude = ( 'employee', 'ack_ip', 'state', 'scheduler', 'scheduler_form', 'hr_status', 'parent', 'root', 'dates',)

# HTML (template)
<!-- Stuff -->
<div id="date-group" class="control-group date-controls">
    <label class="control-label" for="date">Date</label>
    <div class="controls">
        <div class="input-append">
            <input type="text" placeholder="Click to see Calendar"  name="dates[]" class="input-xlarge emp-dates" id="date">
            <span class="add-on"><i class="icon-calendar"></i></span> 
            &nbsp;&nbsp;
            <a id="AddDateBtn" class="btn btn-primary"><i class="icon-plus icon-white"></i> Add additional day</a>
            <br><br>
        </div>
        <div class="pull-left sub-controls">
            <h4>Employee Section</h4>
            <div class="well">
                Reason for Absence:<br>
                <select name="reasons[]" class="emp-reasons">
                    {% for reason in reasons %}
                    <option value="{{ reason.id }}">{{ reason.reason }}</option>
                    {% endfor %}
                </select>
                <span class="add-on emp-reason-help"><i class="icon-question-sign"></i></span> 
                <br><br>
                Hours: <br>
                <select name="hours[]">
                    {% for hour in hours %}
                    <option value="{{ hour }}">{{ hour }}</option>
                    {% endfor %}
                </select>
                <br><br>
                Explanation Required:<br>
                <textarea class="input-xlarge" name="explanations[]"></textarea>
            </div>
        </div>
    </div>
</div>
<!-- Stuff -->

The Employee can have as many of the HTML blocks as they want. Using JavaScript I dynamically "copy" the html that is shown above. This allows the employee to submit multiple dates (dates[], hours[], reasons[], explanations[]). I need to save each date group (dates[i], hours[i], reasons[i], explanations[i]) in the Date Model. Then I need to have a ManyToMany relationship with the Form Model and all the dates that were submitted. How can I do this using ModelForm.

I have seen some code online that suggests that it is possible to do this:

form = EmployeeForm()
if form.is_valid():
    # do relationships here

I am also at a loss of how to get the values form dates[], hours[], reasons[], and explanations[] using Django.


Solution

  • If you really need to store these dates in separate table with ManyToMany relationship, then you should extend your ModelForm a little bit.
    First, specify the class for your dates field, which should inherit from django.forms.ModelMultipleChoiceField. In it, override the clean function, and create all the Date objects there. The function should return the list of ids of created objects (or consider returning the results of parent's clean function, passing it this list of ids). Then in your ModelForm you just specify that the field is of your new class, and then all the validation and saving will work.
    Hope it will help; if I've been unclear, ask more:)