Display nothing instead of "None" in html viewer

Hi ! I’m using an html viewer to display information in a nice manner, printable and easy to read. I followed this great guide to understand what’s going on.

Whenever an empty cell is called by the template it shows “None” which is bad for readability and overall ugly. Is there a way to make it display nothing instead of “None” ?

I created several HTML reports. I mean, ChatGPT created them, so I can´t answer your question.

Have you tried searching the HTML formula for the word “None” and replacing it by " "?

Assuming you’re using the code from the page you linked, you’ll need to change it slightly:

# Finds all data associated with this record
class Find_Data(dict):
  def __missing__(self, key):
    # THE LINES BELOW ARE NEW
    val = getattr(rec, key)
    if val is None:
        val = ""
    return val

# Finds the "Proposal" template in the Templates table
template = Templates.lookupOne(Name="Proposal").Template_Formatting

# Formats the template with fields from this table as well as fields from the referenced table
template.format_map(Find_Data())
1 Like

Hi @Rogerio_Penna,

Are you saying you used ChatGPT to create reports for use in Grist, or for other uses outside Grist.

If you are using them for Grist, I would be very interested in some general steps how you are doing this.

Thanks, David.

Not only for reports, but I also created Risk Matriz, SWOT Matrix, etc, with ChatGPT and HTML WIdget in Grist


SWOT

Sign to your ChatGPT account. Ask him to create a Grist formula for this and that, using Python and HTML.

LOL. I need you to be more specific about what difficulties you are facing.

Does it really work this well ? :open_mouth:

Obviously not. :sweat_smile:

Some stuff work directly. Some stuff work better now with the “self analyzing” o1 Preview and o1 Mini models, rather than GPT 4o.

Often there will be several trial and errors due to things that Grist does differently than standard Python.

Giving examples of formulas, asking what it does, and then telling it to change the formula to do that and that with table X and Y and columns A, B and C will work much better than just telling it to just do something.

If I WAS a coder, I would probably not have the patience to correct ChatGPT multiple times. That said, a programmer might be able to spot errors, correct them himself or tell ChatGPT what to correct, and leave all that code to ChatGPT, accelerating it.

For me, despite all the work correcting and re-correcting (just by saying what is wrong, etc, very few times I spot directly the programming error) it’s simply the difference between DOING it and NOT doing it, because I wouldn´t be able to do it without the tool. Simple as that.

But still… look at the Risk Matrix code… it was all ChatGPT created

def gerar_matriz_riscos():
    from datetime import datetime

    # Obter os departamentos selecionados
    departamentos_selecionados = rec.DepartamentosSelecionados or []

    # Obter o risco selecionado (rec)
    risco_selecionado = rec  # 'rec' é o risco atual na tabela de Riscos

    # Obter a probabilidade e impacto atuais do risco selecionado
    analises_sel = Analise_Risco.lookupRecords(RiscoFK=risco_selecionado.id, sort_by="Data_Analise")
    impacto_atual_sel = analises_sel[-1].ImpactNum if analises_sel else risco_selecionado.Impacto_Inerente_Num
    probabilidade_atual_sel = analises_sel[-1].ProbNum if analises_sel else risco_selecionado.Probabilidade_Inerente_Num

    chave_selecionada = (int(probabilidade_atual_sel), int(impacto_atual_sel))

    # Coletar todos os riscos
    todos_riscos = Riscos.all

    # Dicionários para armazenar os contadores e riscos por célula
    contagem_matriz = {}
    riscos_por_celula = {}

    # Identificar riscos que possuem planos de ação
    planos_de_acoes = Planos_de_Acoes.all
    riscos_com_planos = set()
    for risco_fk in planos_de_acoes.RiscosFK:
        if risco_fk:
            riscos_com_planos.add(risco_fk.id)

    data_atual = datetime.now().date()
    ultrapassados_por_celula = {}
    sem_planos_por_celula = {}
    total_riscos = 0
    riscos_documentados = 0

    for risco in todos_riscos:
        # Filtrar os riscos pelos departamentos selecionados
        if departamentos_selecionados:
            if risco.Departamento not in departamentos_selecionados:
                continue

        # Obter as análises de risco ordenadas por data
        analises = Analise_Risco.lookupRecords(RiscoFK=risco.id, sort_by="Data_Analise")
        impacto_atual = analises[-1].ImpactNum if analises else risco.Impacto_Inerente_Num
        probabilidade_atual = analises[-1].ProbNum if analises else risco.Probabilidade_Inerente_Num

        total_riscos += 1

        # Ignorar riscos com impacto ou probabilidade zero
        if impacto_atual == 0 or probabilidade_atual == 0:
            riscos_documentados += 1
            continue

        impacto_idx = int(impacto_atual)
        probabilidade_idx = int(probabilidade_atual)

        chave = (probabilidade_idx, impacto_idx)
        contagem_matriz[chave] = contagem_matriz.get(chave, 0) + 1

        riscos_por_celula.setdefault(chave, []).append(risco)

        if risco.id not in riscos_com_planos:
            sem_planos_por_celula[chave] = sem_planos_por_celula.get(chave, 0) + 1

        # Verificar se a próxima análise está ultrapassada
        if analises:
            ultima_analise = analises[-1]
            data_proxima = ultima_analise.Data_Proxima_Analise
            if data_proxima and data_proxima < data_atual:
                ultrapassados_por_celula[chave] = ultrapassados_por_celula.get(chave, 0) + 1

    def obter_cor_risco(risk_score):
        if risk_score == 0:
            return ('#d3d3d3', 'Inexistente')
        elif risk_score <= 2:
            return ('#157AFB', 'Muito Baixo')
        elif risk_score <= 5:
            return ('#2AE028', 'Baixo')
        elif risk_score <= 10:
            return ('#E8D62F', 'Moderado')
        elif risk_score <= 16:
            return ('#FD9D28', 'Elevado')
        else:
            return ('#E00A17', 'Extremo')

    max_valor = 5

    html = """
    <table style="border-collapse: separate; border-spacing: 2px; width: 100%; height: 100%; table-layout: fixed;">
    <tr>
        <th rowspan="2" colspan="2" style="background-color: #4f4f4f; width: 80px; height: 100px; border: 1px solid white; 
        border-radius: 8px; box-shadow: inset 0 0 0 2px #3d3d3d; color: white; text-align: center; vertical-align: middle;">
            <div>⚠️ {total_riscos}</div>
            <div>📄 {riscos_documentados}</div>
        </th>
        <th colspan="5" style="background-color: #4f4f4f; color: white; text-align: center; 
        border: 1px solid white; border-radius: 8px; box-shadow: inset 0 0 0 2px #3d3d3d;">IMPACTO</th>
    </tr>
    """.format(total_riscos=total_riscos, riscos_documentados=riscos_documentados)

    html += '<tr>'
    for prob in range(1, max_valor + 1):
        html += f'<th style="background-color: #4f4f4f; color: white; text-align: center; width: 80px; \
        border: 1px solid white; border-radius: 8px; box-shadow: inset 0 0 0 2px #3d3d3d;">{prob}</th>'
    html += '</tr>'

    for i in range(max_valor, 0, -1):
        if i == max_valor:
            html += '<tr>'
            html += '<th rowspan="5" style="background-color: #4f4f4f; color: white; text-align: center; vertical-align: middle; \
            writing-mode: vertical-rl; transform: rotate(180deg); border: 1px solid white; border-radius: 8px; \
            box-shadow: inset 0 0 0 2px #3d3d3d;">PROBABILIDADE</th>'
        html += f'<th style="background-color: #4f4f4f; color: white; text-align: center; width: 40px; \
        border: 1px solid white; border-radius: 8px; box-shadow: inset 0 0 0 2px #3d3d3d;">{i}</th>'

        for prob in range(1, max_valor + 1):
            chave = (prob, i)
            count = contagem_matriz.get(chave, 0)
            risk_score = prob * i
            cor_celula, nivel_risco = obter_cor_risco(risk_score)

            def escurecer_cor(cor):
                cor_rgb = tuple(int(cor.lstrip('#')[j:j+2], 16) for j in (0, 2, 4))
                cor_rgb_escura = tuple(max(int(c * 0.8), 0) for c in cor_rgb)
                return f'rgb{cor_rgb_escura}'

            cor_borda = escurecer_cor(cor_celula)

            # Verificar se a célula é a selecionada
            is_selected_cell = (chave_selecionada == chave)

            cell_style = f'background-color: {cor_celula}; border-radius: 8px; box-shadow: inset 0 0 0 2px {cor_borda}; \
            border: 1px solid white; position: relative; height: 50px;'
            if is_selected_cell:
                cell_style += ' outline: 3px solid black;'

            riscos_na_celula = riscos_por_celula.get(chave, [])
            if riscos_na_celula:
                tooltip_lines = []
                # Agrupar riscos por departamento
                riscos_por_departamento = {}
                for r in riscos_na_celula:
                    dept = r.Departamento.Departamento.upper() if r.Departamento else "SEM DEPARTAMENTO"
                    riscos_por_departamento.setdefault(dept, []).append(r)
                # Ordenar departamentos
                for dept in sorted(riscos_por_departamento):
                    if tooltip_lines:
                        tooltip_lines.append("")  # Adiciona uma linha em branco entre os departamentos
                    tooltip_lines.append(f"🔶 {dept}") 
                    # Ordenar riscos dentro do departamento
                    for r in sorted(riscos_por_departamento[dept], key=lambda x: x.NomeRisco):
                        codigo_risco = "RSK-{:0>4}".format(r.id)
                        tooltip_lines.append(f"  {codigo_risco} : {r.NomeRisco}")
                tooltip_text = '&#013;'.join(tooltip_lines)
            else:
                tooltip_text = "Nenhum risco nesta célula"

            tooltip_text = tooltip_text.replace('"', '&quot;')

            sem_planos = sem_planos_por_celula.get(chave, 0)
            ultrapassados = ultrapassados_por_celula.get(chave, 0)

            html += f'<td style="{cell_style}" title="{tooltip_text}">'
            html += '<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">'

            if count > 0:
                html += f'<div style="background-color: rgba(255, 255, 255, 0.75); border: 2px solid #333; \
                border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; \
                font-weight: bold; font-size: 16px;">{count}</div>'
            else:
                html += '&nbsp;'
            html += '</div>'

            if sem_planos > 0:
                html += f'''
                <div style="
                    position: absolute;
                    top: 2px;
                    right: 2px;
                    background-color: #FF0000;
                    color: white;
                    font-size: 10px;
                    padding: 2px 4px;
                    border-radius: 50%;
                    pointer-events: none;
                    border: 1px solid black;">
                    {sem_planos}
                </div>
                '''

            if ultrapassados > 0:
                html += f'''
                <div style="
                    position: absolute;
                    bottom: 2px;
                    right: 2px;
                    background-color: #FFA500;
                    color: white;
                    font-size: 10px;
                    padding: 2px 4px;
                    border-radius: 50%;
                    pointer-events: none;
                    border: 1px solid black;">
                    {ultrapassados}
                </div>
                '''
            html += '</td>'
        html += '</tr>'

    html += '</table>'

    # Botão Legenda com Tooltip
    html += """
    <div style="margin-top: 10px; display: inline-block;">
        <button style="padding: 5px 10px; border: none; background-color: #4f4f4f; color: white; border-radius: 5px; cursor: pointer;" title="Registro de Riscos
⚠️ Total de Riscos
📄 Somente Documentados
Células da Matriz
⚪ Riscos na célula
🔴 Riscos sem planos de ação
🟠 Riscos com prazo ultrapassado
Graus de Risco
🟦 Muito Baixo
🟩 Baixo
🟨 Moderado
🟧 Elevado
🟥 Extremo">
            Legenda
        </button>
    </div>
    """

    return html

# Chamada da função para gerar a matriz
return gerar_matriz_riscos()