Persistent error "Cannot assign must be an instance"

I’ve been working on a form which corresponds to a model ActionAnswers. One field, id_action, is a foreign key to the Actions model and has consistently given me the same error no matter what I do: Cannot assign “16”: “ActionAnswers.id_action” must be a “Actions” instance.

I understand the concept of using an instance like this as I successfully did it on another form in this project, but I can’t make this one work. Using print statements, I can see that the error happens at form.is_valid(), but even when I remove that statement the same error comes up. I’ve made lots of changes and variations to the POST code, but any suggestions will be helpful.

views.py

def actions(request):
    if request.method == 'POST':
        form = ActionsForm(request.POST)
        if form.is_valid():
            id_action_value = form.cleaned_data['id_action']
            
            action_instance = Actions.objects.get(pk=id_action_value)
            print(action_instance)

            action_answer_instance = form.save(commit=False)
            action_answer_instance.id_action = action_instance  
            action_answer_instance.save()

            return redirect('actions')
    else:
        scores = Scores.objects.filter(id_company=request.user.company_id)
        matching_actions = {}

        for score in scores:
            answer = score.answer
            score_id_question = score.id_question
            conditions = QuestionsActions.objects.filter(
                action_condition=answer, id_question=score_id_question
            )

            for condition in conditions:
                action = condition.id_action
                theme_id = action.id_theme.id  
                theme_name = action.id_theme
                question = Questions.objects.get(id=score_id_question.id)
                action_nb = action.actionnb
                existing_actions = [entry['action'].actionnb for theme_actions in matching_actions.values() for entry in theme_actions]

                if action_nb not in existing_actions:
                    if theme_id not in matching_actions:  
                        matching_actions[theme_id] = []  

                    matching_actions[theme_id].append({
                        'action': action,
                        'matching_condition': condition.action_condition,
                        'id_question': score_id_question.id,
                        'score': score.score,
                        'question': question,
                        'theme_name': theme_name
                    })
        
        # Calculate the total count of actions for each theme
        total_actions = Actions.objects.values('id_theme').annotate(total_count=Count('id_theme'))

        theme_counts = {theme: len(actions) for theme, actions in matching_actions.items()}

        # Calculate the completion percentage for each theme
        action_percentage = {theme['id_theme']: (theme_counts.get(theme['id_theme'], 0) / theme['total_count']) * 100 for theme in total_actions}
        remaining_percentage = {theme_id: 100 - percentage for theme_id, percentage in action_percentage.items()}
        # Sort the dictionary after the loop
        sorted_matching_actions = OrderedDict(sorted(matching_actions.items(), key=lambda x: x[0]))

        form_data = {} 

        for theme_name, entries in matching_actions.items():
            for entry in entries:
                # Use the 'actionnb' as the key for the initial data dictionary
                form_data[entry['action'].actionnb] = entry['action'].name
                form_data[entry['action'].id] = Actions.objects.get(pk=entry['action'].id)

        # Create the form instance with initial data
        form = ActionsForm(initial={'form_data': form_data})
        return render(request, 'planActions.html', {'form': form, 'matching_actions': sorted_matching_actions, 'remaining_percentage': remaining_percentage})

    return render(request, 'planActions.html', {'form': form, 'matching_actions': sorted_matching_actions, 'remaining_percentage': remaining_percentage})

form.py

class ActionsForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for visible in self.visible_fields():
            visible.field.widget.attrs['class'] = 'p-2 border'
        self.fields['note'].widget.attrs['class'] = 'h-11 p-2 border'
        self.fields['id_action'].required = False

    class Meta:
        model = ActionAnswers
        fields = ['id_action', 'priority', 'state', 'startdate', 'duedate', 'enddate', 'managergender', 'managerfirstname', 'managerlastname', 'note']
        widgets = {
            'startdate': forms.DateInput(attrs={'type': 'date'}),
            'duedate': forms.DateInput(attrs={'type': 'date'}),
            'enddate': forms.DateInput(attrs={'type': 'date'}),
            'managerfirstname': forms.TextInput(attrs={'placeholder': 'John'}),
            'managerlastname': forms.TextInput(attrs={'placeholder': 'Doe'}),
            'note': forms.Textarea(attrs={'placeholder': 'Notes'}),

        }
    id_action = forms.IntegerField(widget=forms.HiddenInput(), required=False)
    priority = forms.ChoiceField(
                choices=(('faible', 'Faible'), ('elevee', 'Elevée'), ('urgente', 'Urgente'),),
                required=False
            )
    state = forms.ChoiceField(
        choices=(('A réaliser', 'A réaliser'), ('En cours', 'En cours'), ('Terminé', 'Terminé'),),
        required=False
    )
    managergender = forms.ChoiceField(
        choices=(('M.', 'M.'), ('Mme', 'Mme')),
        required=False
    )
    def clean(self):
        cleaned_date = super().clean()
        start =  cleaned_date.get('startdate',None)
        due = cleaned_date.get('duedate',None)
        end = cleaned_date.get('enddate',None)

        if end and start and end < start:
            self.add_error('due','La date de fin ne peut pas être avant la date de début.')

models.py

class ActionAnswers(models.Model):

    id_company = models.ForeignKey('Companies', models.DO_NOTHING, db_column='id_company')

    id_action = models.ForeignKey('Actions', models.DO_NOTHING, db_column='id_action')

    priority = models.CharField(max_length=255)

    state = models.CharField(max_length=255, blank=True, null=True)

    completion = models.IntegerField()

    initialcompletion = models.IntegerField(db_column='initialCompletion')

    startdate = models.DateTimeField(db_column='startDate')

    duedate = models.DateTimeField(db_column='dueDate', blank=True, null=True)

    enddate = models.DateTimeField(db_column='endDate', blank=True, null=True)

    managergender = models.CharField(db_column='managerGender', max_length=255)

    managerfirstname = models.CharField(db_column='managerFirstName', max_length=255, blank=True, null=True)

    managerlastname = models.CharField(db_column='managerLastName', max_length=255, blank=True, null=True)

    note = models.TextField(blank=True, null=True)

    createdat = models.DateTimeField(db_column='createdAt')

    updatedat = models.DateTimeField(db_column='updatedAt')

Relevant section of planActions.html:

{% for key, value in form.initial.form_data.items %}
                      {% if entry.action.name == value and entry.score is Null %}
                      <h2 class="font-semibold text-xl"> {{ key }}: {{ value }}</h2>
                      <br>
{% comment %} FORM FIELDS {% endcomment %}
                      <form action="{% url 'actions' %}" class="flex flex-col flex-wrap justify-evenly w-full" method="POST" novalidate>
                        {% csrf_token %}
                        {{ form.non_field_errors }}
                        <input type="hidden" name="id_action" value="{{ entry.action.id }}">
                        <div class="flex">
                          <div class="p-2">
                            {{ form.startdate.errors }}
                            <label for="{{ form.startdate.id_for_label }}">Date de début :</label>
                            {{ form.startdate }}
                          </div> 

and the full traceback:

Traceback (most recent call last):
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\contrib\auth\decorators.py", line 23, in _wrapper_view
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\Desktop\rgpd_django\app\views.py", line 109, in actions
    if form.is_valid():
       ^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\forms\forms.py", line 201, in is_valid
    return self.is_bound and not self.errors
                                 ^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\forms\forms.py", line 196, in errors
    self.full_clean()
    ^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\forms\forms.py", line 435, in full_clean
    self._post_clean()
    ^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\forms\models.py", line 479, in _post_clean
    self.instance = construct_instance(
                    
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\forms\models.py", line 83, in construct_instance
    f.save_form_data(instance, cleaned_data[f.name])
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\db\models\fields\__init__.py", line 1035, in save_form_data
    setattr(instance, self.name, data)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jay.bury\AppData\Local\Programs\Python\Python312\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 266, in __set__
    raise ValueError(
    ^

Exception Type: ValueError at /actions/
Exception Value: Cannot assign "1": "ActionAnswers.id_action" must be a "Actions" instance.

There are certain things like

  • In your models you have given id_action as ForeignKey to Actions and Actions is also a Model
  • Next thing in your ActionsForm id_action is included in fields and then id_action is defined as IntegerField, Which in my opinion is not needed because when you render you form in your template it will give you the select field with the options from your Actions model
  • Now when you submit the form instead of this
        if form.is_valid():
            id_action_value = form.cleaned_data['id_action']
            
            action_instance = Actions.objects.get(pk=id_action_value)
            print(action_instance)

            action_answer_instance = form.save(commit=False)
            action_answer_instance.id_action = action_instance  
            action_answer_instance.save()

you can directly save it

        if form.is_valid():
            action_answer_instance = form.save(commit=False)
            # Here you can do other code if you want to
            action_answer_instance.save()

Also commit=False will not create a model instance, so saving other field without creating a instance first might give the issues.

1 Like

This is not an accurate statement.

The exact purpose of using commit=False is to create the instance - but not save it to the database.

1 Like

Sorry for the confusion, instead of model instance I should stated the db instance.

by this line I mean to say so saving other field without creating a instance first might give the issues

        if form.is_valid():
            action_answer_instance = form.save(commit=False)

            # Here you can do other code if you want to
            # assigning action_answer_instance to some other related 
            # field might gives issue i.e
            some_realted_field_instace = action_answer_instance
            some_realted_field_instace.save()
            # In the above code might gives issue like cannot assign
            # action_answer_instance before it is created

            action_answer_instance.save()

@KenWhitesell Thanks for finding that incorrect statement, kindly correct me If I’m going wrong in the above explanation.

There’s still a chance of confusion here because of the specific line:

This just creates another reference to the same model instance, so the next two lines:

would actually do exactly the same thing!

I think what you were more trying to express would be written as:

if form.is_valid():
            action_answer_instance = form.save(commit=False)

            # Here you can do other code if you want to
            # assigning action_answer_instance to some other related 
            # field might gives issue i.e
            other_object.fk_to_action_answer = action_answer_instance
            other_object.save()
            # In the above code might gives issue like cannot assign
            # action_answer_instance before it is created

            action_answer_instance.save()

In which case your thought is 100% correct.

However, I don’t believe this is the case in the original post. I’m not seeing anything creating this type of situation. The only object being saved in the POST section of the view is the action_answer_instance.

Yes, I forget to mention other_object which have F.K. to action_answer.
Thanks :v:

Yes, there is not I was just giving suggestion.
Which leads to all of this :sweat_smile: