foreign key field is returning the queryset values instead of the field value

Hi team,
I need help with the following the issue explained below:

  1. I have two models created in Django models; “Membro” & “Setor” related to tables in postgresql “c_membro” and “c_setor” respectively.
  2. c_membro handle workers and c_setor handle the area the workers belong to.
  3. Originally the primary key for table “c_setor” was id column (type: varchar) and I have a foreign key on table “c_membro” using “id” column from c_setor with column “setor_id” on “c_membro” table. (Originally scripts below)
  4. On “c_setor” table I added a new column “sector_id” (Type: serial4) and set this new column as primary key.
  5. On table c_membro was added a new column “sector_id” and set as a new foreign key with c_setor.sector_id column. (see new scripts below)
  6. Note I did those changes directly on data base, then I added the code on python models and makemigrations and migrate --fake to add those fileds to my model (probably did this is not the best way)
  7. The issue I have is that now when I check the admin session in dejango web the sector_id column returns the entire row referenced not the sector_id value itself.

→ Originaly code on Django project

Models.model

class Setor(models.Model):
    id = models.CharField(primary_key=True, max_length=255)
    nome = models.CharField(max_length=100)

    class Meta:
        db_table = 'c_setor'
        verbose_name = 'Setor'
        verbose_name_plural = 'Setors'

    def __str__(self):
        return f"{self.nome}"




class Membro(models.Model):
    id = models.CharField(primary_key=True, max_length=255)
    nome = models.CharField(max_length=255)
    setor = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)
    cargo = models.ForeignKey(Cargo, on_delete=models.CASCADE, related_name='c_membro', null=True)
    email = models.CharField(max_length=255)  # Adicione o campo de email
    supervisor = models.CharField(max_length=255)
    area = models.CharField(max_length=255)

    class Meta:
        db_table = 'c_membro'
        verbose_name = 'Membro'
        verbose_name_plural = 'Membros'

    def __str__(self):
        return f"{self.id} - {self.nome} - {self.email}"

#----------------------------------------------------------------------------
--View.py

def membros_view(request):

    membros = Membro.objects.all()
    setores = Setor.objects.all()  # Recupere a lista de todos os setores
    setor_filtrado = request.GET.get('setor')  # Obtenha o valor do setor selecionado no filtro

    if setor_filtrado:  # Se um setor foi selecionado no filtro
        membros = membros.filter(setor_id=setor_filtrado)  # Filtrar os membros pelo setor selecionado

    membros = membros.order_by('nome')  # Ordenar os membros pelo nome em ordem alfabética

    #print('hola:', setor_filtrado)
    return render(request, 'membros.html', {'membros': membros, 'setores': setores, 'setor_filtrado': setor_filtrado})



#I have no views for c_setor table.

#------------------------------------------------------------------------------------------------------
--class MembroAdmin

class MembroAdmin(admin.ModelAdmin):
    list_display = ('id', 'nome', 'setor', 'cargo')
    list_filter = ('setor', 'cargo')
    search_fields = ('id', 'nome')

    def get_list_display(self, request):
    # Define as colunas que você deseja exibir em cada linha
    # Exemplo: dividir em 3 colunas
        columns = 4
    # Obtém a lista padrão de campos de exibição
        list_display = super().get_list_display(request)
    # Divide a lista de campos em várias listas de acordo com o número de colunas
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
    # Retorna a lista de campos dividida em colunas
        return sum(column_lists, [])
admin.site.register(Membro, MembroAdmin)


-- on Form.py

class MembroForm(forms.ModelForm):
    class Meta:
        model = Membro
        fields = ['id', 'nome', 'setor', ''
                                         '']  # Lista dos campos que você deseja editar
        widgets = {
            'setor': forms.Select(attrs={'class': 'form-control'}),
            'cargo': forms.Select(attrs={'class': 'form-control'}),
        }

class SetorForm(forms.Form):
    setor = forms.ModelChoiceField(queryset=Setor.objects.all(), empty_label=None)

→ changed mentioned on code on Django project (item 4 and 5)

Models.model

class Setor(models.Model):
    id = models.CharField(max_length=150)
    nome = models.CharField(max_length=100)

    sector_id  = models.AutoField(primary_key=True)
    description = models.CharField(max_length=150)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, related_name = 'se_created_by')
    created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
    last_modified_by = models.ForeignKey(User, on_delete=models.CASCADE, max_length=7, blank=True, null=True,related_name = 'se_last_modified_by')
    last_modified_on = models.DateTimeField(auto_now=True, blank=True, null=True)
    status_ind = models.SmallIntegerField(blank=True, null=True)

    class Meta:
        db_table = 'c_setor'
        verbose_name = 'Setor'
        verbose_name_plural = 'Setors'

    def __str__(self):
        return f"{self.nome} - {self.sector_id} = {self.created_on} = {self.created_by} - {self.last_modified_on} - {self.last_modified_by}"
        
        
class Membro(models.Model):
    id = models.CharField(primary_key=True, max_length=255)
    nome = models.CharField(max_length=255)
    setor = models.CharField(max_length=50, null=True)
    #setor = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)
    cargo = models.ForeignKey(Cargo, on_delete=models.CASCADE, related_name='c_membro', null=True)
    email = models.CharField(max_length=255)  # Adicione o campo de email
    supervisor = models.CharField(max_length=255)
    area = models.CharField(max_length=255)

    sector_id = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)
    created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, related_name='me_created_by')
    last_modified_on = models.DateTimeField(auto_now=True, blank=True, null=True)
    last_modified_by = models.ForeignKey(User, on_delete=models.CASCADE, max_length=7, blank=True, null=True, related_name='me_last_modified_by')
    status_ind = models.SmallIntegerField(blank=True, null=True)


    class Meta:
        db_table = 'c_membro'
        verbose_name = 'Membro'
        verbose_name_plural = 'Membros'

    def __str__(self):
        return f"{self.id} - {self.nome} - {self.email}"
        
#----------------------------------------------------------------------------
--View.py

def membros_view(request):

    membros = Membro.objects.all()
    setores = Setor.objects.all()  # Recupere a lista de todos os setores
    setor_filtrado = request.GET.get('setor')  # Obtenha o valor do setor selecionado no filtro

    if setor_filtrado:  # Se um setor foi selecionado no filtro
        membros = membros.filter(sector_id=setor_filtrado)  # Filtrar os membros pelo setor selecionado
        #print('Hola:', membros)

    membros = membros.order_by('nome')  # Ordenar os membros pelo nome em ordem alfabética

    #print("hola:", setor_filtrado)
    return render(request, 'membros.html', {'membros': membros, 'setores': setores, 'setor_filtrado': setor_filtrado})

#I have no views for c_setor table.

#------------------------------------------------------------------------------------------------------
--class MembroAdmin

class MembroAdmin(admin.ModelAdmin):
    def created_date(self, obj):
        return obj.created_on.strftime('%d-%m-%Y')

    def updated_date(self, obj):
        return obj.last_modified_on.strftime('%d-%m-%Y')


    list_display = ('id', 'nome', 'sector_id', 'cargo', 'created_date', 'created_by', 'updated_date', 'last_modified_by', 'status_ind')
    #list_display = ('id', 'nome', 'setor', 'cargo', 'email', 'supervisor', 'area', 'sector_id', 'created_date', 'created_by', 'updated_date', 'last_modified_by', 'status_ind')
    list_filter = ('sector_id', 'cargo')
    search_fields = ('id', 'nome')

    def get_list_display(self, request):
    # Define as colunas que você deseja exibir em cada linha
    # Exemplo: dividir em 3 colunas
        columns = 9

    # Obtém a lista padrão de campos de exibição
        list_display = super().get_list_display(request)
    # Divide a lista de campos em várias listas de acordo com o número de colunas
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
    # Retorna a lista de campos dividida em colunas
        return sum(column_lists, [])

admin.site.register(Membro, MembroAdmin)


-- No changes   on Form.py -

class MembroForm(forms.ModelForm):
    class Meta:
        model = Membro
        fields = ['id', 'nome', 'setor', ''
                                         '']  # Lista dos campos que você deseja editar
        widgets = {
            'setor': forms.Select(attrs={'class': 'form-control'}),
            'cargo': forms.Select(attrs={'class': 'form-control'}),
        }

class SetorForm(forms.Form):
    setor = forms.ModelChoiceField(queryset=Setor.objects.all(), empty_label=None)


On image below you could find how it shown the column setor before change and how it shown the new column added sector_id

I haven’t followed through everything you’ve written here yet, but one item does jump out at me.

You do not want to name your foreign key field with the name anything_id.

Yes, internally, Django uses the table column with that name, but at the level of the ORM, the name should be anything.

In Django, the field name is a reference to the related model, not a reference to the primary key. Django provides for the implicit reference to the id field of the foreign key without it needing to be explicitly specified.

What this means is that:

should actually be:
sector = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)

You then have the ability to access either sector for the instance of the Setor model, or sector_id to directly reference the primary key of the related model.

(I will admit, I’m having a difficult time trying to figure out what exactly your objective is by doing all this and what you’re trying to achieve as the final result of all these changes.)

Hi @KenWhitesell
Thanks for you reply.

Sorry for my messy comment, let me try to resume and trying to explain me better:
On table c_setor there was a column name “id” as varchar type and on table “c_member” there was a column setor_id that was the foreign key related to “id” column in c_setor table.

#Line code for primaryKey column "id" on c_setor table
id = models.CharField(primary_key=True, max_length=255)

#Line code for foreignKey colum "setor" on c_member table
setor = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)


So, I wanted to use an auto-number column as primary key for c_setor table instead the varchar column “id” it had; to do that, I did all the changes directly on DB by altering tables to add new columns and define new primary and foreign keys.

Then added all those changes on Models and ran python manage.py makemigrations and then I ran python manage.py migrate --fake

So, about your comment below:

Yes, internally, Django uses the table column with that name, but at the level of the ORM, the name should be anything .

Yes, it was my mistake, since I had the new column sector_id added on database table c_membro I thought that when I added the code for that new column in Django Models I would need to use the same name I used on table so that was I added the line code you mentioned
“sector_id = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name=‘c_membro’, null=True)”

The issue is: when I go to the admin section in the web browser the column sector_id shows concatenated values of all columns instead of the value itself, on the white image above on sector_id column I wanted to see the value column sector_id has instead of the concatenated value is shown now.

thxs

So yes, you want to change this back. The field name here should be sector, not sector_id.

1 Like

Dear @KenWhitesell
dully noted. I already did it.

the line code on my Member.Model is as below:

sector = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name=‘c_membro’, null=True)

Please help me with another question when possible. no rush :slight_smile:

I have the admin model for Member as below: (there I have on list_display those fields ‘id’, ‘nome’, ‘sector_id’, ‘sector’, ‘setor’, ‘cargo’)

class MembroAdmin(admin.ModelAdmin):
    def created_date(self, obj):
        return obj.created_on.strftime('%d-%m-%Y')

    def updated_date(self, obj):
        return obj.last_modified_on.strftime('%d-%m-%Y')


    list_display = ('id', 'nome', 'sector_id', 'sector', 'setor', 'cargo')
    list_filter = ('setor', 'cargo')
    search_fields = ('id', 'nome')

    def get_list_display(self, request):
    # Define as colunas que você deseja exibir em cada linha
    # Exemplo: dividir em 3 colunas
        columns = 6

    # Obtém a lista padrão de campos de exibição
        list_display = super().get_list_display(request)
    # Divide a lista de campos em várias listas de acordo com o número de colunas
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
    # Retorna a lista de campos dividida em colunas
        return sum(column_lists, [])

admin.site.register(Membro, MembroAdmin)

So, the question is: how can I show/list the name of the sector (setor.name) on admin section on webpage, as it was before the change? for both on the table and on filter column on left side.
For example for column “sector id = 2” I want to have a column Sector = Carroceria CVP

Note column setor I dont need it, I will not show it, It is only there now for reference

In your list_display, you would use sector__nome to access the nome field of the Setor object being referenced by the sector foreign key in the Membro model. (See The Django admin site | Django documentation | Django)

You can use the same identifier (sector__nome) in your list_filter list. (See ModelAdmin List Filters | Django documentation | Django)

1 Like

Hi @KenWhitesell
Thanks you much, you really helped me a lot, I have been able to understood and continue learning django, I really apreciated.

Hi @KenWhitesell

Just to try to understand, probably I am being so annoying, but it seems that when change the primaryKey on models, django “lose” the reference between models.

Originally:
on Setor.models the primaryKey was id column type=varchar, I changed to sector_id column type=autofiled.
On Membro.models foreignKey was setor colum, I changed to sector column.

After applying that change, on webpage when adding a new membro the ForeignKey column sector on Membro model shown all the values related to record that matches with that id. See image, I posted on comment oct 25th.

So you advise me to use sector__nome

for testing, I created a testing app with the following models and admin

#model
class Setor(models.Model):
    sector_id = models.AutoField(primary_key=True)
    nome = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.nome}"


class Membro(models.Model):
    id = models.CharField(primary_key=True, max_length=255)
    nome = models.CharField(max_length=255)
    sector = models.ForeignKey(Setor, on_delete=models.CASCADE, related_name='c_membro', null=True)
    email = models.CharField(max_length=255)  # Adicione o campo de email

    def __str__(self):
        return f"{self.id} - {self.nome} - {self.email}"

admin



#admin

@admin.register(Setor)
class SetorAdmin(admin.ModelAdmin):
    list_display    = ('sector_id', 'nome')

    def get_list_display(self, request):
    # Define as colunas que você deseja exibir em cada linha
    # Exemplo: dividir em 3 colunas
        columns = 2
    # Obtém a lista padrão de campos de exibiçãothem
        list_display = super().get_list_display(request)
    # Divide a lista de campos em várias listas de acordo com o número de colunas
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
    # Retorna a lista de campos dividida em colunas
        return sum(column_lists, [])


@admin.register(Membro)
class MembroAdmin(admin.ModelAdmin):
    list_display    = ('id', 'nome', 'sector', 'email')
    list_filter = ('sector', 'nome')
    search_fields = ('id', 'nome')

    def get_list_display(self, request):
    # Define as colunas que você deseja exibir em cada linha
    # Exemplo: dividir em 3 colunas
        columns = 4
    # Obtém a lista padrão de campos de exibiçãothem
        list_display = super().get_list_display(request)
    # Divide a lista de campos em várias listas de acordo com o número de colunas
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
    # Retorna a lista de campos dividida em colunas
        return sum(column_lists, [])

as you can see on the list_display I just added the field as they are on the models, I not use sector__nome and on web page the app show the single value for ForeignKey sector on Membro model

I’m sorry, I’m not sure I’m following you here.

Is there a question or issue for which you are wanting an answer?

If so, please explain in more detail what you are asking about.

Hi @KenWhitesell

It hard to me to explain, but indeed It is a question, not an issue
it just that you commented the following:

In your list_display , you would use sector__nome to access the nome field of the Setor object being referenced by the sector foreign key in the Membro model.

but on the code I shared on my previous comment, on list_display I did not use sector__nome just add the fields name into the list_display, but it access the nome field of the Setor object being referenced by the sector foreign key in the Membro model.

I just want to understand why for this It was not needed to use sector__nome


@admin.register(Membro)
class MembroAdmin(admin.ModelAdmin):
    list_display    = ('id', 'nome', 'sector', 'email')
    list_filter = ('sector', 'nome')
    search_fields = ('id', 'nome')

    def get_list_display(self, request):
        columns = 4
        list_display = super().get_list_display(request)
        column_lists = [[] for _ in range(columns)]
        for i, field_name in enumerate(list_display):
            column_lists[i % columns].append(field_name)
        return sum(column_lists, [])

on the image below, related to MembroAdim class referenced aboce you can see the sector is showing the reference nome field, not the id eventhoug I did not use the sector

Yes and no.

More accurately, when the Admin is rendering a foreign key field, it renders the __str__ method of that model.

In this particular case, yes, __str__ returns self.nome.

If you were to change your __str__ method to return something else, you would see the change in what’s rendered.

1 Like

Dear @KenWhitesell

you are the best buddy!
Thank you so much, now I am clear about it and how it works. I really appreciated you take some time to explain it to me.