1

My question is: is there a way to create custom model form that will use a specified field from a related model rather than the related model's id?

To clarify, if I have the following two models:

class ModelOne(models.Model):
  id = models.AutoField(primary_key = True)
  name = models.CharField(unique = True, blank = False, null = False)

class ModelTwo(models.Model):
  id = models.AutoField(primary_key = True)
  parent = models.ForeignKey(ModelOne, blank = False, null = False)
  attribute_1 = models.CharField(blank = False, null = False)
  attribute_2 = models.IntegerField(blank = False, null = False)

Now if I create an UpdateView on ModelTwo using a ModelForm then the parent field will be pre-filled with the corresponding id from ModelOne. However I want it to display the name attribute of ModelOne and then on form submission parse the unique name (of ModelOne) to the corresponding instance of ModelOne. The reason I want to do it this way, is that I believe it is far more intuitive from a users perspective to deal with the name of ModelOne (when updating a ModelTwo instance) rather than its "id".

Any suggestions of how I can do this?

enrishi
  • 2,913
  • 3
  • 16
  • 9

2 Answers2

1

Firstly, try defining the unicode method on ModelOne. It might not apply to the solution, but it's worth having - it will drive the text values in a form Select widget...

def __unicode__(self):
    '''Warning: be careful not to use related objects here,
    could cause unwanted DB hits when debugging/logging
    '''
    return self.name

If that's not sufficient, something like this might work (it is adapted from a form I have that updates the user's name attached to a profile)...

class M2Form(forms.ModelForm):
    m1_name = forms.CharField()

    class Meta:
        model = ModelTwo

    def save(self, *args, **kw):
        # Update your name field here, something like
        if self.cleaned_data.get('m1_name'):
            self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('m1_name'))
            return super(M2Form, self).save(*args, **kw)

This is untested, and you'll likely need to adapt this to validate that the name exists and make sure the original parent field doesn't appear on the form. With any luck, the first answer covers what I think your question is.

Rog
  • 4,075
  • 2
  • 24
  • 35
0

Using Rog's answer as a starting point and delving through some of Django's internals I eventually came to a working solution. Given my level of Django knowledge, I imagine there is a better way of doing this; so if you have another method please add it.

So based on the above two models, I created the following form class:

class CustomForm(forms.ModelForm):
    parent = models.CharField(label='Name')

    class Meta:
       model = ModelTwo
       exclude = ['parent']

    def __init__(self,*args,**kwargs):
       # The line of code below is the one that I was looking for. It pre-populates 
       # the "parent" field of the form with the "name" attribute of the related 
       # ModelOne instance.
       kwargs['initial']['parent'] = kwargs['instance'].parent.name
       super(CustomForm,self).__init__(*args,**kwargs)
       # The next line is for convenience and orders the form fields in our desired 
       # order. I got this tip from: 
       # http://stackoverflow.com/questions/913589/django-forms-inheritance-and-order-of-form-fields
       self.fields.keyOrder = ['parent','attribute_1','attribute_2']

    def save(self, *args, **kwargs):
       if self.cleaned_data.get('parent'):
       # This section of code is important because we need to convert back from the 
       # unique 'name' attribute of ModelOne to the corresponding instance so that
       # ModelTwo can be saved. Thanks goes to Rog for this section of code.
           self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('parent'))
           return super(CustomForm, self).save(*args, **kwargs)
enrishi
  • 2,913
  • 3
  • 16
  • 9