1

UPDATED using Basti's answer view

I have a model for Phones (PhoneMixin - for use with any other model) and for Person:

from django.utils.translation import ugettext_lazy as _
from django.db import models

class PhoneMixin(models.Model):
    TYPES_CHOICES = (
        ('HOME', u'Home'),
        ('WORK', u'Work'),
        ('MOBILE', u'Mobile'),
        ('HOME_FAX', u'Fax (home)'),
        ('WORK_FAX', u'Fax (work)'),
        ('PAGER', u'Pager'),
        ('OTHER', u'Other')
    )
    phone_type = models.CharField(_('Type'), max_length=20, choices=TYPES_CHOICES)
    number = models.CharField(_('Number'), max_length=40)
    comment = models.TextField(_('Comment'), blank=True, null=True)

class Person(models.Model):
    class Meta:
        verbose_name = u'Person'
        verbose_name_plural = u'People'

    name = models.CharField(max_length=255, verbose_name=u'Name')
    email = models.EmailField(max_length=75)

    def __unicode__(self):
        return self.name

class PhonePerson(PhoneMixin):
    belongs_to = models.ForeignKey(Person)

I also have their forms:

from django import forms
from models import Person, PhoneMixin

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person

class PhoneForm(forms.ModelForm):
    class Meta:
        model = PhoneMixin

And my only view is:

from django.forms.formsets import formset_factory
from django.http import HttpResponseRedirect
from django.shortcuts import render
from forms import PhoneForm, PersonForm


def person_insert_view(request):
    PhoneFormset = formset_factory(PhoneForm)

    if request.method == 'POST':  # If the form has been submitted...
        phone_formset = PhoneFormset(request.POST, prefix='phone_formset')
        person_form = PersonForm(request.POST, prefix='person_form')
        if person_form.is_valid():  # All validation rules pass
            person = form.save(commit=False)
            phone_formset = PhoneFormset(request.POST, instance=person)
            if phone_formset.is_valid():
                phone_formset.save()
                person.save()
            return HttpResponseRedirect('/')  # Redirect after POST
        else:
            return render_to_response('function_based_person.html',{
        'person_form': person_form,
        'phone_formset': phone_formset,
    })
    else:
        person_form = PersonForm()
        phone_formset = PhoneFormset(prefix='phone_formset')

    return render(request, 'function_based_person.html', {
        'person_form': person_form,
        'phone_formset': phone_formset,
    })

And the function_based_person.html template:

<html>
<body>
<form enctype="multipart/form-data" method="post" action=".">
{% csrf_token %}
{% if person_form.visible_fields %}
    {% for field in person_form.visible_fields %}
    <div class="control-group{% if field.errors %} error{% endif %}">
        <label class="control-label" for="id_{{ field.html_name }}">{{ field.label }}    </label>
        <div class="controls">
            {{ field }}
            {% if field.errors %}
            <span class="help-inline">{% for error in field.errors %}{{ error }}{% endfor %}</span>
            {% endif %}
            {% if field.help_text %}<span class="help-inline">{{ field.help_text }}</span>{% endif %}
        </div>
    </div>
    {% endfor %}

    {% for phone_form in phone_formset %}
      {{phone_form.as_table}}
    {% endfor %}
    <div class="form-actions">
        <button type="submit" class="btn btn-primary">Save</button>
    </div>
{% endif %} 
</form>
</body>
</html>

The problem is that I want to have at least one phone in my Person, and have the possibility to have 2 or more, it the user wants to add it.

staticdev
  • 2,950
  • 8
  • 42
  • 66

1 Answers1

2

A ForeignKey is simply a pointer to specific row in another table. What your Person model says is "Every person has one or two telephones. Each telephone is stored in another table called 'Phone'." So, unless you are doing something sophisticated, your Person form is asking you to associate two existing Phones for your new Person.

(editing to respond to your comment).
If what you want to do is to have multiple phones, then what you need is a formset

The first thing you need to do is to change which table points at which: instead of the person pointing to a phone, the phone should point to a person. So, for example, add belongs_to = models.ForeignKey(Person) to your phone model declaration.

The next step is to actually ask for the phones in your view. Here is where formsets come in. In your views, create a formset:

#in views.py
from django.forms.formsets import formset_factory
PhoneFormset = formset_factory(PhoneForm, extras = 1)

if request.method == 'POST':
    phone_formset = PhoneFormset(request.POST, prefix = 'phone_formset')
    person_form = PersonForm(request.POST, prefix='person_form')
    # validate your forms, etc
else:
    person_form = PersonForm()
    phone_formset = PhoneFormset(prefix = 'phone_formset')

Add phone_formset to the dictionary passed to your template, end render it as so:

# in your template
<form>
{{person_form.as_p}}
{% for phone_form in phone_formset %}
  {{phone_form.as_table}}
{% endfor %}
</form>

The last thing you need to do is to set each phone's belongs_to field to the Person who submitted the form. You will have to do this in your view: maybe save your person before saving your phones, get their pk, set the Phone's foreign key and then save it.

Basti
  • 252
  • 2
  • 9
  • Correct Basti! I don't want to use ForeignKey. It more like a List of Phone. It's no problem for the Phones to be in another table, I don't want to specify the max number of Phone a Person have. I just don't know how to do that... – staticdev Jul 19 '13 at 14:33
  • 1
    Edited to better address your problem. – Basti Jul 19 '13 at 15:46
  • Sorry, I didn't understand this views part.. are you using function-based views? I'm using generic views. I also edited my question for being more clear. Thanks! – staticdev Jul 19 '13 at 19:04
  • I don't think you should use generic views. I suggest you use a view like the one from the docs: https://docs.djangoproject.com/en/1.5/topics/forms/#using-a-form-in-a-view – Basti Jul 19 '13 at 19:38
  • I've created a testing application with your answer and updated my models, forms and template. For the sake of completeness, the line in your answer: PhoneFormset = formset_factory(PhoneForm, extras = 1), actually its 'extra' attribute not 'extras'. The problem is that it is showing only one phone using this code. I want to have at least one. – staticdev Jul 27 '13 at 07:01
  • I also created a Mixin for Phone, so that I can use in any other model. – staticdev Jul 27 '13 at 07:16
  • 1
    Great job. I assume that what you mean by "I want to have at least one" is that you want your user to see a page with one phone's form, and be able to add more dynamically? If this is the case, there is no easy pre-built way to do this. What most people would do is write some javascript to dynamically add a form, like here: http://stackoverflow.com/questions/501719/dynamically-adding-a-form-to-a-django-formset-with-ajax. I also found this example on how to do it with no javascript: http://stackoverflow.com/questions/2448970/django-adding-inline-formset-rows-without-javascript – Basti Jul 27 '13 at 15:39
  • I don't need it to be dynamic, just keep it simple (2 phone forms, 1 required). There seems to be a problem when I try to save it.. I get a ValidationError: [u'ManagementForm data is missing or has been tampered with'] =/ – staticdev Jul 28 '13 at 20:45