djangomodelchoicefield

Django ModelChoiceField is not filled when editing


I have ProjectForm form in which one field is ModelChoiceField with foreign key to Company Model. Creating new record and saving is not a problem, but when I want to edit that record this field is empy (like if none company was selected, but in DB I can see it with correct selection), other fields are filled correctly.

I want to avoid changing template code as much as possible so its generated by universal form template.

----- models.py -----

class Company(models.Model):
    name = models.CharField(max_length=250, unique=True)
    ...

class Project(models.Model):
    company = models.ForeignKey(Company, on_delete=models.SET_NULL, null=True, blank=True)
    name = models.CharField(max_length=250, blank=False)
    description = models.TextField()
    ...

----- forms.py -----

@parsleyfy
class ProjectForm(forms.Form):
    company = forms.ModelChoiceField(label='Společnost', queryset=Company.objects.all(), required=False)
    name = forms.CharField(label='Název')
    ...

----- view.py -----

def edit(request, pk):
    context = {'form_url': reverse('projects:project-edit', args=(pk,)),
               'form_type':'edit'}

    obj = get_object_or_404(Project, pk=pk)
    if request.method == "GET":
        form = ProjectForm(initial=obj.__dict__)
    else:
        form = ProjectForm(request.POST, request.FILES)
        if form.is_valid():
            try:
                obj.update(**form.cleaned_data)
            except IntegrityError:
                errors = form._errors.setdefault("name", ErrorList())
                errors.append(u"Toto jméno již existuje")
            else:
                return redirect(LIST_URL) # SUCCESSFULLY SAVED

    context['form'] = form
    return render(request, FORM_TEMPLATE, context)

----- template.html -----

        <form  role="form" action="{{ form_url }}" method="post" enctype="multipart/form-data"
               data-parsley-validate novalidate>
            {% include 'projects/reusable/form_template.html' %}
        </form>

in which:
    {% elif field|input_type == 'Select' %}
        <div for="{{ field.label }}" class="col-sm-2 col-form-label">
            {{ field.label_tag }}
        </div>
        <div class="col-sm-10">
            {{ field|add_classes:'form-control'}}
            {% if field.help_text %}
                <small class="form-text text-muted">{{ field.help_text }}</small>
            {% endif %}
        </div>

----- DB screenshot -----

DB screenshot

----- Form screenshot ----- (first input is the company)

enter image description here


Solution

  • initial=obj.__dict__ will return the project dict with

    {
      ...
      company_id: value
    } 
    

    in your form, you defined field name as company though. Therefore, you need update your form initial with value same as company_id, thus the company field will be properly populated

    {
      company: value
    }
    

    Use model_to_dict can explicitly remove non-editable fields, check and get the ids of foreign keys, you can read more on its source since is not well documented.

    def model_to_dict(instance, fields=None, exclude=None):
        """
        Return a dict containing the data in ``instance`` suitable for passing as
        a Form's ``initial`` keyword argument.
        """
        pass
    
    
    from django.forms.models import model_to_dict
    
    # model_to_dict will handle 'special' fields properly
    form = ProjectForm(initial=model_to_dict(obj))