How to work with MultipleCheckBox with Django?

Question:

I’m new to Django and I’m trying to make an application that registers the attendance of entrepreneurs (I’m currently working on this). There are some services that I would like to select, sometimes the same person requires more than one service per appointment. However, part of the application uses the Models and part uses the Forms, I’d like to keep the two ones separate to keep the code organized, but I have no idea how to do it, I even created a separate class just for the tuple that holds the values, but no I managed to implement, can anyone help me? Here are the codes:

models.py

from django.db import models
from django_cpf_cnpj.fields import CPFField, CNPJField    
    
class CadastroEmpreendedor(models.Model):
    
    ABERTURA = 'ABERTURA MEI'
    ALTERACAO = 'ALTERAÇÃO CADASTRAL'
    INFO = 'INFORMAÇÕES'
    DAS = 'EMISSÃO DAS'
    PARC = 'PARCELAMENTO'
    EMISSAO_PARC = 'EMISSÃO DE PARCELA'
    CODIGO = 'CÓDIGO DE ACESSO'
    REGULARIZE = 'REGULARIZE'
    BAIXA = 'BAIXA MEI'
    CANCELADO = 'REGISTRO BAIXADO'
        
    descricao_atendimento = (
        (ABERTURA, 'FORMALIZAÇÃO'),
        (ALTERACAO, 'ALTERAÇÃO CADASTRAL'),
        (INFO, 'INFORMAÇÕES'),
        (DAS, 'EMISSÃO DAS'),
        (PARC, 'PARCELAMENTO'),
        (EMISSAO_PARC, 'EMISSÃO DE PARCELA'),
        (CODIGO, 'CÓDIGO DE ACESSO'),
        (REGULARIZE, 'REGULARIZE'),
        (BAIXA, 'BAIXA MEI'),
        (CANCELADO, 'REGISTRO BAIXADO'),
    )
    
    cnpj = CNPJField('CNPJ')
    cpf = CPFField('CPF')
    nome = models.CharField('Nome', max_length=120)
    nascimento = models.DateField()
    email = models.EmailField('Email', max_length=100)
    telefone_principal = models.CharField(max_length=11)
    telefone_alternativo = models.CharField(max_length=11, blank=True)
    descricao_atendimento
       
    
    def __str__(self) -> str:
        return self.nome
    
class DescricaoAtendimento(models.Model):
     
    descricao = models.ForeignKey(CadastroEmpreendedor, on_delete=models.CASCADE)

forms.py

from django import forms
from .models import DescricaoAtendimento
    
class EmpreendedorForm(forms.ModelForm):
    class Meta:
        model = DescricaoAtendimento
        fields = ['descricao']
        widgets = {'descricao': forms.CheckboxSelectMultiple(),}

views.py

from django.shortcuts import render
from django.contrib import messages
    
from .forms import EmpreendedorForm
    
def cadastro_empreendedor(request):
    if str(request.method) == 'POST':
        form = EmpreendedorForm(request.POST, request.FILES)
            
        if form.is_valid():   
            form.save()
            messages.success(request, 'Produto salvo com sucesso!')
            form = EmpreendedorForm()
        else:
            messages.success(request, 'Erro ao salvar produto!')
    else:
        form = EmpreendedorForm()
    context = {
        'form': form
    }

    return render(request, 'empreendedor.html', context)

If you have any tips, I really appreciate it, I started with Django almost a month ago, so there’s a long way to go.

P.S.: I integrated with PostgreSQL and in the Django administration part I can save all the fields in the DB, but I can’t implement that part of the checkbox.

At this moment, I get the error:

It is impossible to add a non-nullable field 'descricao' to descricaoatendimento without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit and manually define a default value in models.py.

In the template, I gonna work with bootstrap4 to create the forms. But I’d like to resolve this before. I’m still learning English, so sorry for some mistakes.

Asked By: João Marcos

||

Answers:

It sounds like you have many Entrepreneurs, each of which can choose many Services. This is a ManyToMany Relationship and you can create it in Django by having one model for each and creating the link between them like this

class CadastroEmpreendedor(models.Model):
    ...
    descricao_atendimento = models.ManyToManyField(DescricaoAtendimento)

class DescricaoAtendimento(models.Model):
    nome = models.CharField('Nome', max_length=120, default="unnamed service")
     

In this case, every object/row in DescricaoAtendimento is a service. Each entrepeneur can have many services associated with them.

This way you don’t need to create a model form for DescricaoAtendimento to choose services for an entrepeneur. As it’s linked to them by a manytomany relationship you can have an CadastroEmpreendedor model form with just the escricao_atendimento field and the various services become available as options.

Django handles this by creating a ‘through table’ which is basically a table with two fields of foreign keys, one pointing to an entrepeneur, and the other to a service. You can also create this table yourself as a through table – which is useful if you want to extend data about the relationship – eg, a begin and end date for the entrepeneur’s use of a service.

The error you are getting when you migrate isn’t an error, per se. It seems you created an number of DescricaoAtendimento objects and then added the descricao field later. When you then try and migrate, django wants you to provide a default value for the already existing rows, or allow the field to be empty (via blank=True in the model). You can assign a dummy value and then go back and change it in /admin later, or, if you don’t have a lot of data, recreate your database and remigrate. Above I’ve used a default value to avoid this situation.

However, if you are dead set against extra tables, you might want to look at an extension like django multiselectfield

Answered By: SamSparx