ValueError: needs to have a value for field "id" before this many-to-many relationship can be used.

Hi folks, first-time Djangoer here. I’m building a site which has quite complex models, including some many-to-many fields. When saving a new entry into a model, Django throws ValueError: "<CameraModel: Canon Canonflex>" needs to have a value for field "id" before this many-to-many relationship can be used. I understand this is because I need to save the first model before I can use the id of the new record in the second model, but I’m struggling with how to actually make it do this. I’ve looked at examples but I can’t really relate them to my models.

Am I correct that I need to override def save in my form? If someone could give an example that would be great - thanks.

# models.py

class CameraModel(models.Model):
  manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE, 
  help_text='Manufacturer of this camera model')
  model = models.CharField(help_text='The model name of the camera', max_length=45)
  # ...about 20 others fields here...
  shutter_speeds = models.ManyToManyField(ShutterSpeed, blank=True)
  metering_modes = models.ManyToManyField(MeteringMode, blank=True)
  exposure_programs = models.ManyToManyField(ExposureProgram, blank=True)

class MeteringMode(models.Model):
  name = models.CharField(help_text='Name of metering mode as defined by EXIF tag MeteringMode', max_length=45)
  def __str__(self):
    return self.name
  class Meta:
    verbose_name_plural = "metering modes"

class ExposureProgram(models.Model):
  name = models.CharField(help_text='Name of exposure program as defined by EXIF tag ExposureProgram', max_length=45) 
  def __str__(self):
    return self.name
  class Meta:
    verbose_name_plural = "exposure programs"
# forms.py

class CameraModelForm(ModelForm):
    class Meta:
        model = CameraModel
        fields = '__all__'
    def __init__(self, *args, **kwargs):
        super(CameraModelForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout = Layout(
            Fieldset(
                'Basics',
                'manufacturer',
                'model',
                'disambiguation',
                'introduced',
                'discontinued',
                'format',
                'negative_size',
            ),
            Div(
                TabHolder(
                    Tab('Interchangeable lens',
                        Fieldset(
                            'Lens mount',
                            'mount',
                        ),
                    ),
                    Tab('Fixed lens',
                        Fieldset(
                            'Lens',
                            'lens_manufacturer',
                            'lens_model_name',
                        ),
                        Fieldset(
                            'Optics',
                            'zoom',
                            AppendedText('min_focal_length', 'mm'),
                            AppendedText('max_focal_length', 'mm'),
                            AppendedText('closest_focus', 'cm'),
                            PrependedText('max_aperture', 'f/'),
                            PrependedText('min_aperture', 'f/'),
                            'elements',
                            'groups',
                            AppendedText('nominal_min_angle_diag', '&deg;'),
                            AppendedText('nominal_max_angle_diag', '&deg;'),
                            'rectilinear',
                            AppendedText('image_circle', 'mm'),
                            'formula',
                            'aperture_blades',
                            'coating',
                            AppendedText('magnification', '&times;'),
                        ),
                        Fieldset(
                            'Physical',
                            AppendedText('filter_thread', 'mm'),
                            'hood',
                        ),
                        Fieldset(
                            'Misc',
                            'exif_lenstype',
                        ),
                    ),
                ),
                css_class="border",    
            ),
            Fieldset(
                'Physical',
                'body_type',
                AppendedText('weight', 'g'),
            ),
            Fieldset(
                'Focus',
                'focus_type',
                AppendedText('viewfinder_coverage', '%'),
                'af_points',
            ),
            Fieldset(
                'Metering',
                'metering',
                'metering_type',
                InlineCheckboxes('metering_modes'),
                InlineCheckboxes('exposure_programs'),
                'min_iso',
                'max_iso',
                'meter_min_ev',
                'meter_max_ev',
            ),
            Fieldset(
                'Shutter',
                'shutter_type',
                'shutter_model',
                InlineCheckboxes('shutter_speeds'),
                'bulb',
                'time',
            ),
            Fieldset(
                'Flash',
                'int_flash',
                'int_flash_gn',
                'ext_flash',
                'flash_metering',
                'pc_sync',
                'shoe',
                'x_sync',
            ),
            Fieldset(
                'Battery',
                'battery_qty',
                'battery_type',
            ),
            Fieldset(
                'Features',
                'cable_release',
                'dof_preview',
                'mirror_lockup',
                'tripod',
                'power_drive',
                AppendedText('continuous_fps', 'fps'),
            ),
            Fieldset(
                'Misc',
                'notes',
            ),
            FormActions(
                Submit('save', 'Save changes'),
                Button('cancel', 'Cancel')
            )
        )

I suspect your error is happening in views.py and we cannot see that.

override the form_valid() function in the affected view (assuming you are using a Class based View), then save the object like so:

def form_valid(self, form):
        """If the form is valid, save the associated model."""

        self.object = form.save(commit=False)
        self.object.save()
        return HttpResponseRedirect(self.get_success_url())

Try it again and see if that fixed it. (Ah … just realized this was 2-years ago). Oh well.