2

I am trying to do something that I have never seen done before with django, I am trying to make a model field(path_choices) that shows all of the unique path_names from my google sheet in a choice box so that the user can select one of them. However when I tried to make my choices CharField I am getting the error:


ERRORS:

dashboard.Robot.path_choices: (fields.E005) 'choices' must be an iterable containing (actual value, human readable name) tuples.

Right now the google sheet that I am trying to pull from with gspread only has two path-names, so if anybody has any idea on what is causing this problem or what I can do better with this, the help would be appreciated! My Code (UPDATED CODE):

from django.db import models

class Robot(models.Model):
    name = models.CharField(max_length=100)
    status_choices = [('driving', 'driving'), ('waiting', 'waiting'), ('stuck', 'stuck')]
    status = models.CharField(choices=status_choices, max_length=7, default='waiting')
    path_choices = models.CharField(max_length=255)

My Form:

from django import forms
from django import forms
from .models import Robot
import gspread
from oauth2client.service_account import ServiceAccountCredentials

class RobotForm(forms.ModelForm):
    def _generate_choices():
        scope = ["REDACTED",'REDACTED',"REDACTED","REDACTED"]
        creds = ServiceAccountCredentials.from_json_keyfile_name("dashboard/Files/creds.json", scope)
        client = gspread.authorize(creds)
        sheet = client.open("tutorial").sheet1
        path_name_fetch =  sheet.col_values(1)
        path_names = []
        temp_list = []
        path_options = []
        for i in path_name_fetch:
            if i not in path_names:
                path_names.append(i)
        for path_name_options in path_names:
            temp_list.append(f'{path_name_options}')
            temp_list.append(f'{path_name_options}')
            path_options.append(tuple(temp_list))
        
    path_choices = forms.ChoiceField(choices=_generate_choices())
    class Meta:
        model = Robot
        fields = {'path_choices'}
Reacher42
  • 31
  • 3
  • Are you sure that the problem isn't from gspread(perhaps not fetching properly etc)? – Garberchov Sep 02 '21 at 14:39
  • 1
    @Garberchov, that is not the problem because I have tested this code in just a straight python file and it has worked without a problem. – Reacher42 Sep 02 '21 at 14:41

1 Answers1

2

What you're trying to do may not be in line with the intended use of Choices. Whenever the possibilities of a Choice change at the Model level, new migrations must be made.

Your implementation of Choice for Robot.status is static and in line with the example in the Django documentation.

If instead you wanted to use a dynamic Choice for your path_choices that is retrieved from Google Sheets, I would recommend doing this in the ModelForm using a ChoiceField. According to the documentation, the available choices can come from a callable, which would be your path_options wrapped in a function. path_options should then become a CharField without choices, since you manage those in the submission, rather than at the model level.

Models.py

class Robot(models.Model):
    ...
    path_choices = models.CharField(max_length=255)

ModelForms.py

class RobotForm(ModelForm):
      def _generate_choices():
            # Query GSpread
            choices = [('choice','choice'), ...]
            return choices

      path_choices = forms.ChoiceField(choices=_generate_choices())
      class Meta:
            model = Robot
            fields = ['path_choices', ...]
  • 1
    I am really confused by what you mean in this, in how `path_options` would become wrapped in a function but also become a `char_field` Do you have an example of this besides that documentation as that example doesn't seem as well sell suited to this use? – Reacher42 Sep 02 '21 at 14:33
  • 1
    A `CharField` does not have any restrictions on the content that is saved aside from being a string of characters, you limit the inputs by defining `choices`. By moving the logic away from the model, and into the modelform, you can dynamically change the choices that are submitted to the path_options. – MadCowDisease Sep 02 '21 at 14:39
  • 1
    I am still confused as I am already using a `CharField`, is there a documentation or example that you can show that shows this more clearly? – Reacher42 Sep 02 '21 at 14:42
  • I've updated my answer with examples. I have limited experience with ModelForms, but this is the gist of what I mean. The key difference is that a ChoiceField in a Form (and ModelForm) can be dynamic: "If the argument is a callable, it is **evaluated each time the field’s form is initialized**, in addition to during rendering. Defaults to an empty list." – MadCowDisease Sep 02 '21 at 14:54
  • Thanks a bunch, that makes much more sense as I didn't sink in that the model form would be a separate file. Will give it a shot – Reacher42 Sep 02 '21 at 15:08
  • I assumed you were already using ModelForms to expose your Model to the user through a web interface, either way I hope this helps and do let me know if it doesn't work. – MadCowDisease Sep 02 '21 at 15:14
  • It didn't work, I got the error `TypeError: 'NoneType' object is not iterable`, I think it is a problem with the form but I don't know what I did wrong. – Reacher42 Sep 02 '21 at 17:07
  • 1
    There is no drop down box on the image, I don't know what I did wrong? https://imgur.com/a/ihPqKLZ – Reacher42 Sep 02 '21 at 21:36
  • This is probably because the underlying model has a `CharField` instead of a `ChoiceField`. Have you tried explicitly defining the HTML Widget that should be created in the ModelForm? `path_choices = forms.ChoiceField(widget=forms.Select, choices=_generate_choices())` – MadCowDisease Sep 03 '21 at 11:09