User Model in forms choicefield not working

Django 5.1.1
Python 3.12.4
Bootstrap 5.3.3

Details: Trying to create a select dropdown with the Django username(s) as selections.

NOTE: There are only 2 users in Django User

Error I’m getting: ValueError: too many values to unpack (expected 2)

models.py (partial)

class ToLog(models.Model):
  noc_tech_name = models.CharField(max_length=50, blank=True, null=True)

forms.py (partial)

from django.contrib.auth.models import User

class ToLogForm(forms.ModelForm):
    noc_tech_name = forms.ChoiceField(choices = [(x.get_username()) for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))

html page (partial)

<div class="input-group input-group-sm mb-2 w-100">
   <label class="input-group-text" for="{{ form.noc_tech_name.id_for_label }}">NOC Tech :** 
   </label>
   {{ form.noc_tech_name }}
   {% for error in form.noc_tech_name.errors %}
   <span class="text-danger">{{ error }}</span>
   {% endfor %}
</div>

What happens when you remove the brackets around the final values in the generator above?
example:
[x.get_username() for x in User.objects.all()]

Why are you using a ModelForm if all you wanted was just that dropdown?

The code is partial and there are other things that need modelform.

When I remove brackets as u suggested i get the same error.

noc_tech_name = forms.ChoiceField(choices = [x.get_username() for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))

ValueError: too many values to unpack (expected 2)

Oh, I get it … that thing is unpacking strings instead ot tuples. My first suggestion was silly – sorry about that. Your initial construct omitted the Key or id values

[ 
    (key1, username1),
    (key2, username2),
]

Use the above to reconstruct your generator. You were omitting the keys. Try the following:

[(x.id, x.get_username()) for x in User.objects.all()]

Was it really necessary to use a CharField there (above)? How about:

from django.contrib.auth.models import User

class ToLog(models.Model):
    noc_tech_name = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        blank=True, 
        null=True
    )
    
    def __str__(self):
        return self.noc_tech_name.username

then you can use the Meta class in ModelForm to specify the dropdwon field.

from .models import ToLog

class ToLogForm(forms.ModelForm):
    class Meta:
        model = ToLog
        fields = ("noc_tech_name",)
        widgets = {
            "noc_tech_name": forms.Select(attrs={'class': 'form-control'}),
        }

One problem with this though … you are allowing null values for User which could yield unexpected behaviours

Hi onyeibo,
I tried your change adding the (x.id, x.get_username()) :

noc_tech_name = forms.ChoiceField(choices = [(x.id, x.get_username()) for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))

This works but when I save a record it saves the x.id value in the db and not the username. Is there a way to make it save the username?

Also your next Idea (foreignkey) looks very nice I will try it after I fix the above issue. I also cannot have it deleting the records (cascade) bcuz the records also serve as history record for building new tickets in the system.

I really appreciate all your advise on this.
Thank you

Hi,

I also notice that it’s not updating the user roster when new users are added without shutting down the app. So I created a def___init__ (see below) so it would refresh every time the form is load. Only problem is it does not work at all.

def __init__(self, *args, **kwargs):
        super(ToLogForm, self).__init__(*args, **kwargs)
        noc_tech_name = forms.ChoiceField(choices = [(x.id, x.get_username()) for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))

Any ideas of what’s wrong?
Thank you.

You probably need to describe what you are trying to do in greater detail, then share more code so that we get a clearer picture of your dilemma.

For instance, we need to see how the View class is processing the submitted form (views.py)

All code involved is below.
Right now it all works well except when the ‘noc_tech_name’ field is saved it stores the x.id value and not the ‘username’ value.
and when a new user record is added thru admin it does not show up in select unless I shut down the app and restart it.
I also added a **def init(self, *args, kwargs): to the forms class ToLogForm to refresh the users on every form load, but is not working at all.

Goal: Form with select control that has current Django users as ChoiceField option to select.

index_2.html (partial)

<div class="input-group input-group-sm mb-2 w-100">
                <label class="input-group-text" for="{{ form.noc_tech_name.id_for_label }}">NOC Tech :**</label>
                {{ form.noc_tech_name }}
                {% for error in form.noc_tech_name.errors %}
                  <span class="text-danger">{{ error }}</span>
                {% endfor %}
              </div>

forms.py (partial)

class ToLogForm(forms.ModelForm):
    
    EntryType(models.IntegerChoices):' in models.py
    entrytype = forms.ChoiceField(choices=[[1, 'Control_M'],[2, 'SolarWinds'],[3, 'FYI'],[4, 'Other'],[5, 'Adhoc']],
                                  widget = forms.Select(attrs={'class': 'form-control'})) 
     
    noc_tech_name = forms.ChoiceField(choices = [(x.id, x.get_username()) for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))
    
     #validation
    def clean(self):
        super(ToLogForm, self).clean()
        social_title = self.cleaned_data.get('social_title')
        
        if len(social_title)<4:
            self.add_error('social_title','Can not save social_title less than 4 characters long')
            self.fields['social_title'].widget.attrs.update({'class': 'form-control  is-invalid'})
        return self.cleaned_data
    
    
    def __init__(self, *args, **kwargs):
        super(ToLogForm, self).__init__(*args, **kwargs)
        noc_tech_name = forms.ChoiceField(choices = [(x.id, x.get_username()) for x in User.objects.all()], widget = forms.Select(attrs={'class': 'form-control'}))

models.py (partial)

class ToLog(models.Model):
  class EntryType(models.IntegerChoices):
      control_m	= 1, "Control_M"							                    
      solarwinds = 2, "SolarWinds"							                  
      fyi = 3, "FYI"          							              
      other = 4, "Other" 	
      adhoc = 5, "Adhoc"						                   
  social_title = models.CharField(max_length=200, null=True)
  ivanti_ticket = models.IntegerField(db_column='ivanti_ticket', blank=True, null=True)
  date_time = models.DateTimeField(auto_now_add=True)
  completed = models.BooleanField(default=False)
  comments = models.TextField(blank=True, null=True)
  noc_tech_name = models.CharField(max_length=50, blank=True, null=True)
  entrytype = models.PositiveSmallIntegerField(choices=EntryType.choices, blank=True, null=True)
        
def __str__(self):
    return self.social_title

views.py (partial)

def edit_turn(request, pk):                        
    if request.method == 'GET':                                   
        tologs = ToLog.objects.get(pk=pk)                             
        context = {}
        context['tologs'] = tologs
        context['form'] = ToLogForm(initial={
            'social_title': tologs.social_title,
            'ivanti_ticket': tologs.ivanti_ticket,
            'date_time': tologs.date_time,
            'completed': tologs.completed,
            'comments' : tologs.comments,
            'noc_tech_name' : tologs.noc_tech_name,
            'entrytype' : tologs.entrytype
        })
        template = 'turnover_app/index_2.html'                                          
        return render(request, template, context)
    else:                                            
        tologs = ToLog.objects.get(pk=pk)
        form = ToLogForm(request.POST, instance=tologs)
        if form.is_valid():
            form.save()                                                 
            #print('save')                                               
            template = 'turnover_app/index_2.html'
            return render(request, template, {'form': form,'tologs': tologs})
        else:
            template = 'turnover_app/index_2.html'
            return render(request, template, {'form': form,'tologs': tologs})

Why don’t you have a foreign key? This all seems very complex and incorrectly modeled…

1 Like

you can set choices in cahrfield.

# forms.py
class ToLogForm(forms.ModelForm):
    # remove
    # entrytype = forms.ChoiceField(choices=[[1, 'Control_M'],[2, 'SolarWinds'],[3, 'FYI'],[4, 'Other'],[5, 'Adhoc']],
# models.py
class ToLog(models.Model):
  entrytype = models.CharField(choices=[['Control_M', 'Control_M'],[ 'SolarWinds', 'SolarWinds'],[ 'FYI', 'FYI'],['Other', 'Other'], blank=True, null=True)

@jbhrfx9280 after going through your construct, it seems you made things unneccessarily complex.

  1. Yes, the suggested solution saves x.id which is the Primary Key (pk) of the User. That is exactly what you need to store and not the username. You use that to access the username by grabbing the User (e.g. ToLog.objects.get(noc_tech_name=x).username where x is what you stored). I am assuming a Foreignkey relationship with User model here.
  2. You can always change on_delete value to models.DO_NOTHING or models.SET_NULL depending on your expected behaviour.
  3. When you use a foreignkey, you do not need to restart the app.

Therefore, I propose the following:

models.py:

from django.db import models
from django.contrib.auth.models import User

ENTRY_TYPE = [
    (1, "Control_M"),
    (2, "SolarWinds"),
    (3, "FYI"),
    (4, "Other"),
    (5, "Adhoc"),
]

class ToLog(models.Model):
    user = models.ForeignKey(
        User, 
        on_delete=models.DO_NOTHING, 
        blank=True, 
        null=True
    )
    entrytype = models.PositiveSmallIntegerField(choices=ENTRY_TYPE, blank=True, null=True)
    social_title = models.CharField(max_length=200, null=True)
    ivanti_ticket = models.IntegerField(db_column='ivanti_ticket', blank=True, null=True)
    date_time = models.DateTimeField(auto_now_add=True)
    completed = models.BooleanField(default=False)
    comments = models.TextField(blank=True, null=True) 

    def __str__(self):
        return self.user.username

forms.py:

from django import forms
from .models import ToLog

class ToLogForm(forms.ModelForm):
    class Meta:
        model = ToLog
        # add as many field names as you want to appear on the form
        fields = ("user", "entrytype",) 
        widgets = {
            "user": forms.Select(attrs={'class': 'form-control'}),
            "entrytype": forms.Select(attrs={'class': 'form-control'}),
        }

views.py

from .forms import ToLogForm

def edit_turn(request, pk): 
    tologs = ToLog.objects.get(pk=pk)
    template = 'turnover_app/index_2.html'                        
    if request.method == 'GET':                       
        context = {
            'tologs': tologs,
            'form': ToLogForm(initial={
                'social_title': tologs.social_title,
                'ivanti_ticket': tologs.ivanti_ticket,
                'date_time': tologs.date_time,
                'completed': tologs.completed,
                'comments' : tologs.comments,
                'noc_tech_name' : tologs.noc_tech_name,
                'entrytype' : tologs.entrytype,
            }.
        }                                                 
    else:                                            
        form = ToLogForm(request.POST, instance=tologs)
        context = {'form': form, 'tologs': tologs})
        if form.is_valid():
            form.save() 
            # possible redirection here                                                                                              
    return render(request, template, context)

Note that your view will continue to render (loop) until you provide a way out – perhaps an anchor (<a></a>) button in index_2.html pointing to another view or an explicit view-name for redirection when the form is valid. Have you considered having a default value for the “entrytype” choice selection? That may be the desired behaviour rather than a blank/null.

entrytype = models.PositiveSmallIntegerField(choices=ENTRY_TYPE, default=4)

The above will set entrytype to “Other” if the user does not make a choice selection. You can take this approach further by including a “None” option in ENTRY_TYPE for such defaults

Hi onyeibo,
I made the foreignkey change as u suggested and I get this error when trying to save a record

ValueError: Cannot assign "'2'": "ToLog.noc_tech_name" must be a "User" instance.

Any ideas?
Thank you

Never mind - I figured out I had a syntax error.
Thank you so much for helping a newbie. I have now changed over to a foreignkey method.
Thank you