CupertinoListTile é um controle similar ao ListTile mas seguindo a estilização do iOS. O controle possui a propriedade notched que, se marcada como False (default) renderiza o controle no modo padrão do iOS, ou, se marcada como True gera o controle na aparência de “Inset Grouped”, o mesmo usado em iOS Notes ou Reminders.
Ele exibe uma linha de altura fixa com controles internos, geralmente imagens, ícones, textos e menus. Ele admite ícones à direita ou à esquerda do título, podendo seus objetos interiores responder a certos eventos.
Um exemplo mínimo de código aparece listado abaixo, com a ilustração de seu resultado ao ser executado.
import flet as ft
def main(page: ft.Page):
page.bgcolor="#ABE6DB"
cuperLT_1=ft.CupertinoListTile(
title=ft.Text("Patches de recuperação do Windows 12", color="BLACK"),
subtitle=ft.Text("Equipe Mostarda", color="#333355"),
additional_info=ft.Text("Completar tarefa até 21:45H", color="#333355"),
leading=ft.Icon(name=ft.cupertino_icons.CHAT_BUBBLE),
trailing=ft.Icon(name=ft.cupertino_icons.BELL_CIRCLE_FILL),
bgcolor="#BDAB8D"
)
cuperLT_2= ft.CupertinoListTile(
title=ft.Text("Patches de recuperação do iOS 17", color="BLACK"),
subtitle=ft.Text("Equipe Catchup", color="#333355"),
additional_info=ft.Text("Completar tarefa até 09:15H", color="#333355"),
leading=ft.Icon(name=ft.cupertino_icons.CHAT_BUBBLE_2),
trailing=ft.Icon(name=ft.cupertino_icons.BELL_CIRCLE, color="RED"),
bgcolor="#AD9B7D"
)
page.add(cuperLT_1, cuperLT_2)
ft.app(target=main)
Propriedades de CupertinoListTile
Propriedade
Descrição
additional_info
controle a ser exibido à direita do título mas antes do controle ao final da linha. Com frequência é usado um texto, seguido por um ícone, embora isso não seja obrigatório.
bgcolor_activated
cor de fundo do controle após ter sido clicado ou tocado.
leading
A Control para exibir antes do title.
leading_size
restringe largura e altura do controle. Default é 28.0.
leading_to_title
espaço horizontal entre leading e title. Default é 16.0.
notched
booleano. Se notched=True o controle será criado com a forma de um “Inset Grouped”, usado em aplicativos do iOS como Notes ou Reminders. Default é False.
padding
espaçamento interno no controle CupertinoListTile, descrevendo distância entre os controles internos: title, subtitle, additional_info e trailing.* Veja a propriedade Container.padding para mais informações e possíveis valores.
subtitle
conteúdo adicional exibido abaixo do título. Em geral é usado um controle de texto.
title
usado para exibir o conteúdo principal no controle CupertinoListTile. Em geral é usado um controle de texto.
toggle_inputs
booleano. Se toggle_inputs=True um clique na lista de altera o estado dos controles internos Radio, Checkbox ou Switch. Default é False.
trailing
controle a ser exibido após o título. Normalmente é usado um controle de ícone.
url
URL usada para localizar um recurso quando a lista recebe um clique. Se o evento on_click estiver registrado (se houver uma função a ele associada), o evento é disparado.
url_target
como abrir URL no modo web. Pode ser:
_blank: o recurso é aberto em nova aba ou janela (default).
_self: o recurso é aberto na aba ou janela atual.
Evento de CupertinoListTile
on_click
dispara quando o usuário clica ou toca na lista.
Apesar de não encontrar documentação a respeito, na minha experiência os nomes dos ícones podem ser idênticos aos nomes do flutter, mas escritas em maiúsculas. Exemplos são:
ft.cupertino_icons.FOLDER,
ft.cupertino_icons.FUNCTION,
ft.cupertino_icons.HAMMER
Um aplicativo que busca ícones cupertino pode ser encontrada em Cupertino Icons.
Exemplo de uso de CupertinoListTile
import flet as ft
def main(page: ft.Page):
page.bgcolor="#ABE6DB"
def click(e):
if e.control.notched:
clt2.data +=1
txt = f"Esse controle foi clicado {clt2.data} vez{'es' if clt2.data>1 else ''}."
clt2.subtitle=ft.Text(txt, color="#333355")
else:
clt1.data +=1
txt = f"Esse controle foi clicado {clt1.data} vez{'es' if clt1.data>1 else ''}."
clt1.subtitle=ft.Text(txt, color="#333355")
titulo.value =f"Os controles foram clicados {clt1.data+clt2.data} vezes."
page.update()
clt1=ft.CupertinoListTile(
title=ft.Text("Patches de recuperação do Windows 12", color="BLACK"),
subtitle=ft.Text(
"Esse controle ainda não foi clicado.",
color="#333355"
),
additional_info=ft.Text("Completar tarefa até 21:45H", color="#333355"),
leading=ft.Icon(name=ft.cupertino_icons.CAR_DETAILED, color="BLACK"),
trailing=ft.Icon(name=ft.cupertino_icons.WRENCH, color="RED", size=30),
padding=ft.padding.only(15, 20, 30, 40),
bgcolor="#BDAB8D",
bgcolor_activated=ft.colors.AMBER_600,
data=0,
on_click=click,
)
clt2=ft.CupertinoListTile(
title=ft.Text("Patches de recuperação do iOS 17", color="BLACK"),
subtitle=ft.Text(
"Esse controle ainda não foi clicado.",
color="#333355"
),
additional_info=ft.Text("Completar tarefa até 09:15H", color="#333355"),
leading=ft.Icon(
name=ft.cupertino_icons.CAR_DETAILED,
color="BLUE",
size=30,
),
trailing=ft.Icon(
name=ft.cupertino_icons.GAUGE,
color="RED",
size=30,
),
padding=20,
bgcolor="#CDBB9D",
bgcolor_activated=ft.colors.AMBER_100,
notched=True,
data=0,
on_click=click,
)
titulo=ft.Text(
"Nenhum controle foi clicado até agora",
color="#0b4b8f",
size=30,
weight=ft.FontWeight.BOLD,
)
ct=ft.Container(
content=titulo,
margin=5,
padding=5,
alignment=ft.alignment.center,
bgcolor="#95d3c8",
width=850,
height=50,
border_radius=10,
)
page.add(ct, clt1, clt2)
ft.app(target=main)
Valem as observações: um container é inserido na cabeçalho, contendo um título que usa titulo=ft.Text(...). A variável titulo foi definida separadamente para estar disponível para alterações na função click().
As duas tiles foram definidas com a propriedade data em clt=ft.CupertinoListTile(..., data=0), usada para contar quantos cliques foram aplicados sobre o controle. Essa mesma propriedade é usada para construir a propriedade clt.subtitle, também atualizada a cada clique.
A função click() distingue entre os dois controles através da propriedade e.control.notched, que só é verdadeira no segundo controle. Desta forma não é necessário criar uma função para cada controle.
Outros controles de Layout: GridView e ResponsiveRow
Controle GridView
GridView, assim como ListView, é um controle apropriado para apresentar listas longas (milhares de ítens). Esses controles devem ser escolhidos ao invés de Column ou Row para se obter um rolamento suave de conteúdo. O Navegador de Ícones (Icons Brower) do Flet é construído com um GridView.
Um exemplo curto do uso do controle é mostrado abaixo. Nesse código um array (uma lista) de 8 containeres com cores de fundo escolhidas entre os valores da lista, cores. Essa lista é passada em flet.GridView.controls = array.
import flet as ft
cores = {0:"black", 1:"#1D74FF", 2:"#C51709", 3:"#FFE000",
4:"#00CB4F",5:"#A866DC", 6:"#FF6600", 7:"#145375"}
def main(page: ft.Page):
array = []
for i in range(0, 8):
array.append(
ft.Container(
bgcolor=cores[i],
border=ft.border.all(1, "BLACK"),
border_radius=ft.border_radius.all(15),
)
)
grid_imagens = ft.GridView(
controls = array,
expand=1,
runs_count=15,
max_extent=100,
child_aspect_ratio=1.0,
spacing=2,
run_spacing=2,
)
page.add(grid_imagens)
ft.app(target=main)
A execução desse código resulta na tela mostrada na figura 1.
Propriedades de GridView
Propriedade
Descrição
auto_scroll
booleano, auto_scroll=True se a barra de rolamento se move automaticamente para a posição onde o último filho foi inserido. Essa propriedade deve ser False para que o método scroll_to() funcione.
child_aspect_ratio
proporção entre dimensões vertical e horizontal (cross-axis e main-axis) de cada filho.
controls
lista de controles a serem renderizados dentro do GridView.
horizontal
booleano, horizontal=True para que a grade empilhe itens horizontalmente.
max_extent
largura ou altura máxima de cada ítem na grade.
on_scroll_interval
intervalo em milisegundos para o disparo do evento on_scroll. Default é 10.
padding
espaço interno de separação entre os filhos e a grade.
No exemplo abaixo usamos o controle flet.Image(src, fit, repeat, border_radius) que ainda não descrevemos nessas notas. Seu objetivo é o de inserir imagens, no caso atual lidas no repositório Image Gallery localizada em picsum.photos.
Nesse código o controle flet.GridView é criado com o array de controles vazio e depois populado pela função carregar_imagens() que insere 12 imagens na propriedade GridView.controls. A propriedade titulo.data armazena a posição das imagens baixadas e é incrementada de 12 a cada disparo do botão bt_mais.
Controle ResponsiveRow
ResponsiveRow traz para o Flet o mesmo conceito de layout de grade usado no framework Bootstrap. Com ele se pode alinhar os controles filhos em colunas virtuais que podem ser redimensionadas. Por padrão uma grade possui 12 colunas, número que pode ser modificado na propriedade ResponsiveRow.columns.
Cada coluna inserida em um ResponsiveRow possui a propriedade col especificando quantas colunas cada controle deve preencher, de modo análogo ao uso da propriedade expand. Por exemplo, para criar um layout que contém duas colunas abrangendo 6 colunas virtuais cada usamos:
import flet as ft
ft.ResponsiveRow([
ft.Column(col=6, controls=[ft.Text("Coluna 1")]),
ft.Column(col=6, controls=[ft.Text("Coluna 2")])
])
O controle é dito “responsivo” porque pode ajustar o tamanho de seus filhos à geometrias variáveis de telas, que pode ser a página, a janela, etc. No exemplo acima a propriedade col é um número constante, significando que o filho ocupará 6 colunas para qualquer tamanho de tela. Se a propriedade col de um filho não for especificada ele terá o número máximo de colunas.
A propriedade col pode ser configurada para ter um valor diferente para “pontos de interrupção” (breakpoints) específicos, de acordo com o tamanho da tela. Os pontos de interrupção, como intervalos de dimensão, recebem nomes:
Por exemplo, no código abaixo o conteúdo é colapsado em apenas uma coluna em uma tela pequena (como a de um telefone celular) e assume 2 colunas em telas grandes:
import flet as ft
ft.ResponsiveRow([
ft.Column(col={"sm": 6}, controls=[ft.Text("Column 1")]),
ft.Column(col={"sm": 6}, controls=[ft.Text("Column 2")])
])
Propriedades de ResponsiveRow
Propriedade
Descrição
alignment
alinhamento horizontal dos controles filhos. Por exemplo, MainAxisAlignment.START (que é o default) posiciona os filhos à esquerda da linha.
Os valores possíveis da propriedade estão no enum MainAxisAlignment com os valores:
START (default)
END
CENTER
SPACE_BETWEEN
SPACE_AROUND
SPACE_EVENLY
columns
o número de colunas virtuais a usar no layout dos filhos. Default é columns=12.
controls
uma lista de controles a serem exibidos dentro do ResponsiveRow.
run_spacing
espaçamento entre as linhas quando o conteúdo está quebrado em múltiplas linhas. Default run_spacing=10.
spacing
espaçamento entre os controles em uma linha. Default é spacing=10 (pixeis virtuais). Esse espaçamento só é aplicado quando o alinhamento (alignment) é start, end ou center.
vertical_alignment
como os controles filhos são posicionados verticalmente.
Os valores possíveis da propriedade estão no enum CrossAxisAlignment com os valores:
START (default)
CENTER
END
STRETCH
BASELINE
Exemplo de uso de ResponsiveRow
No exemplo abaixo tanto as funções coluna() como linha() retornam containeres com um texto. Esses controles preenchem as posições de coluna do ResponsiveRow, a primeira delas especificando os pontos de quebra em col={"sm": 6, "md": 4, "xl": 2}, a segunda em col={"md": 4}. Observe que Container não tem a propriedade col, exceto quando dentro de uma “linha responsiva”.
A propriedade de largura da página page.width é exibida dentro de um Container inserido na camada page.overlay para flutuar em cima dos controles da página. Esse controle está definido em espaçamento fixo bottom=50 e right=50, podendo se sobrepor aos controles da camada abaixo. A atualização é disparada no evento page.on_resize.
Observe que a atualização realizada no redimensionamento altera info.value e a renderização é exposta na página com info.update(). O mesmo efeito seria obtido com page.update() pois esse método cuida de atualizar apenas as partes modificadas da página.
O resultado da execução do código aparece nas figuras 3 e 4.
Row (linha) é um controle que serve de container para outros controles e os exibe horizontalmente. Ele possui propriedades, eventos e métodos relativos ao layout e gerenciamento de rolagem da página (scroll) para evitar overflow (quando o conteúdo ocupa áerea maior que a disponível na página).
Propriedades de Row
Propriedades
Descrição
alignment
alinhamento horizontal dos filhos.
A propriedade MainAxisAlignment recebe um ENUM com os valores:
START(default) alinha à esquerda da Linha
END
CENTER
SPACE_BETWEEN
SPACE_AROUND
SPACE_EVENLY
auto_scroll
booleano, auto_scroll=True para mover a posição de scroll para o final quando ocorre atualização dos filhos. Para que o método scroll_to() funcione é necessário ajustar auto_scroll=False.
controls
uma lista de controles a serem exibidos na linha.
run_spacing
espaçamento entre as várias linhas (runs) quando wrap=True. Default: 10.
Linhas adicionais aparecem quando os controles não cabem dentro de uma linha única.
scroll
permite rolagem horizontal da linha para evitar overflow. A propriedade pode receber o ENUM ScrollMode com os valores:
None (default): não é rolagem e pode haver overflow.
AUTO: rolagem habilitada e a barra de rolagem só aparece quando ocorre o scroll.
ADAPTIVE: rolagem habilitada e a barra sempre visível quando aplicativo roda na web ou desktop.
ALWAYS: rolagem habilitada e a barra sempre visível.
HIDDEN: rolagem habilitada e a barra sempre oculta.
spacing
espaçamento entre controles na linha. Default: 10 pixeis. O espaçamento só é aplicado quando alignment é start, end ou center.
on_scroll_interval
limitação para o evento on_scroll em milisegundos. Default: 10.
tight
booleano, espaço a ser ocupado horizontalmente. Default: tight = False, usar todo o espaço os controles.
vertical_alignment
alinhamento vertical. A propriedade pode receber o ENUM CrossAxisAlignment os valores:
START (default)
CENTER
END
STRETCH
BASELINE
wrap
booleano, se wrap=True os controles filhos serão colocados em linhas adicionais (chamadas runs), caso não caibam em uma única linha.
Métodos de Row
Método
Descrição
scroll_to(offset, delta, key, duration, curve)
move a posição de rolagem para o offset absoluto, para um salto (delta) ou para o controle com key especificada.
Detalhes são idênticos ao do método de Column.scroll_to().
Eventos de Row
Evento
Dispara quando
on_scroll
a posição de rolagem da linha é alterada por um usuário. Consulte Column.on_scroll() para detalhes e exemplos do evento.
Uso das propriedades de row
No código abaixo são criados 30 controles container numerados que são dispostos em uma linha. Dois controle de deslizar (slide) ajustam as propriedades row.width (o número de colunas em cada linha) e row.spacing (o espaçamento entre cada objeto. Quando o número de objetos em uma linha é maior que o espaço permite, novas linhas são inseridas (runs).
import flet as ft # era 52
def main(page: ft.Page):
def items(count):
items = []
for i in range(1, count + 1):
items.append(ft.Container(ft.Text(value=str(i), color="white", size=20), alignment=ft.alignment.center,
width=40, height=40, bgcolor="#40A4D2", border_radius=ft.border_radius.all(10)))
return items
def muda_largura(e):
linha.width = float(e.control.value)
linha.update()
def muda_separa(e):
linha.spacing = int(e.control.value)
linha.update()
slid_separa = ft.Slider(min=0, max=50, divisions=50, value=0, label="{value}", on_change=muda_separa,)
slid_largura = ft.Slider(min=0, max=page.window_width, divisions=20, value=page.window_width,
label="{value}", on_change=muda_largura,)
linha = ft.Row(wrap=True, spacing=10, run_spacing=10, controls=items(30), width=page.window_width,)
txt1 = ft.Text("O primeiro controle seleciona o número de colunas:")
txt2 = ft.Text("O segundo controle seleciona espaçamento entre colunas:")
page.add(ft.Column([txt1, slid_largura,]),ft.Column([txt2, slid_separa,]), linha,)
ft.app(target=main)
O código gera, em algum ajuste dos controles de deslizar, a janela abaixo.
Column é um controle que serve de container para outros controles e os exibe verticalmente. Ele possui propriedades eventos e métodos relativos ao layout e gerenciamento do rolamento da página (scroll) para evitar overflow (quando o conteúdo ocupa áerea maior que a disponível na página.
Propriedades de Column
Propriedades
Descrição
alignment
define como os controles filhos devem ser colocados verticalmente.
A propriedade MainAxisAlignment recebe um ENUM com os valores:
START(default) alinha à esquerda da Linha
END
CENTER
SPACE_BETWEEN
SPACE_AROUND
SPACE_EVENLY
auto_scroll
auto_scroll=True para mover a posição de scroll o final quando ocorre atualização dos filhos. Para que o método scroll_to() funcione devemos fazer auto_scroll=False.
controls
lista de controles a serem exibidos na coluna.
horizontal_alignment
posicionamento horizontal dos controles filhos.
A propriedade recebe o ENUM CrossAxisAlignment com os valores:
START(default) alinha à esquerda da Linha
END
CENTER
STRETCH
BASELINE
on_scroll_interval
limita o evento on_scroll (em milisegundos). Default: 10.
scroll
habilita a rolagem vertical na coluna para evitar overflow de conteúdo.
A propriedade recebe um ENUM opcional ScrollMode com valores:
None (default): coluna não é rolável e pode haver overflow.
AUTO: rolagem habilitada e a barra de rolagem só aparece quando a rolagem é necessária.
ADAPTIVE: rolagem habilitada e a barra sempre visível em aplicativos na web ou desktop.
ALWAYS: rolagem habilitada e a barra sempre vivível.
HIDDEN: rolagem habilitada, a barra de rolagem está sempre oculta.
spacing
espaçamento entre os controles da coluna. Default: 10 pixeis. O espaçamento só é aplicado quando alignment = start, end, center.
run_spacing
espaçamento entre “runs” quando wrap=True. Default: 10.
tight
espaçamento vertical. Default: False (alocar todo o espaço para filhos).
wrap
se wrap=True a coluna distribuirá os controles filho em colunas adicionais (runs) se não couberem em uma coluna.
Métodos de Column
Método
Descrição
scroll_to(
offset, delta,
key, duration,
curve)
move a posição de rolagem para o offset absoluto, para um salto (delta) ou para o controle com key especificada.
Por exemplo:
(1) offset é a posição do controle, um valor entre a extensão mínima e máxima do controle de scroll.
(2) offset negativo conta a partir do final do scroll. offset=-1 para posicionar no final.
(3) delta move o scroll relativo à posição atual. Rola para trás, se negativo.
(4) key move o scroll para a posição especificada com key.
A maioria dos controles do Flet tem a propriedade key (equivalente ao global key do Flutter. keydeve ser única para toda a page/view. duration define a duração da animação de rolagem, em milisegundos. Default: 0 (sem animação). curve configura a curva de animação. Default: ft.AnimationCurve.EASE.
Eventos de Column
Evento
Dispara quando
on_scroll
a posição de rolagem é alterada por um usuário.
O argumento do gerenciador de eventos é instância da ft.OnScrollEvent com as propriedades:
event_type (str), tipo do evento de rolagem:
start: início da rolagem;
update: controle de rolagem mudou de posição;
end: início da rolagem (parou de rolar);
user: usuário mudou a direção da rolagem;
over: controle ficou inalterado por estar no início ou final;
pixels(float): posição de rolagem atual, em pixeis lógicos.
min_scroll_extent (float): valor mínimo no intervalo permitido, em pixeis.
max_scroll_extent (float): valor máximo no intervalo permitido, em pixeis.
viewport_dimension (float): extensão da viewport.
scroll_delta (float): distância rolada, em pixeis. Definido apenas em eventos update.
direction (str) : direção da rolagem: idle, forward, reverse. Definido apenas em evento user.
overscroll (float): número de pixeis não rolados, por overflow. Definido apenas em eventos over.
velocity (float): velocidade na posição de ScrollPosition quando ocorreu overscroll. Definido apenas em eventos over.
Expandindo controles na linha e na coluna
Todos os controles possuem a propriedade expand que serve para expandi-lo para preencher os espaços disponíveis dentro de uma linha. Ela pode receber um booleano ou um inteiro. expand=True significa expandir o controle para preencher todo o espaço. expand=int introduz um “fator de expansão” especificando como dividir um espaço com outros controles expandidos na mesma linha, ou coluna. O código:
cria uma linha contendo um TextField que ocupa o espaço disponível, e um ElevatedButton, sem expansão. A expansão é calculada em termos do tamanho de todos os controle na linha. É possível fornecer fatores que definem a proporção de expansão de cada controle. Por exemplo:
linha = ft.Row([
ft.Container(expand=1, content=ft.Text("A")),
ft.Container(expand=3, content=ft.Text("B")),
ft.Container(expand=1, content=ft.Text("C"))
])
cria uma linha com 3 controles ocupando 1, 3, 1 partes em 5, como exibido na figura,
Percentualmente a largura resultante de um filho é calculada como largura = expand / soma( todas as expands) * 100%.
Da mesma forma um controle filho em uma coluna pode ser expandido para preencher o espaço vertical disponível. Por exemplo, o código abaixo cria uma coluna com um Container que ocupando todo o espaço disponível e um controle Text na parte inferior servindo como uma barra de status:
coluna = ft.Column([
ft.Container(expand=True, content=ft.Text("Esse será expandido")),
ft.Text("Um texto usado como label")
])
Assim como no controle das linhas, podemos usar fatores numéricos em expand=n.
A palavra container do inglês é traduzida como contêiner (pt-br), plural contêineres. Para não criar confusão com a palavra técnica nós o chamaremos aqui por container, containers.
Um aplicativo do Flet abre sempre uma page que serve de container para o objeto View. Uma View é criado automaticamente quando uma nova sessão é iniciada. Ela é basicamente uma coluna (column) básica, que abriga todos os demais controles que serão inseridos na página. Dessa forma ele tem comportamento semelhante ao de uma column, e as mesmas propriedades. Uma descrição resumida será apresentada aqui. Para maiores detalhes consulte a descrição de column.
O objeto View é o componente visual de uma página Flet, responsável por renderizar os elementos da UI e gerenciar seu estado. Ele pode abrigar outros objetos como botões, campos de texto, imagens, etc, e organizá-los em uma estrutura hierárquica. Esses elementos são então renderizados na tela. O objeto View também possui métodos para lidar com eventos do usuário, como cliques em botões ou textos digitados nas caixas de texto.
Por exemplo, o código:
page.controls.append(ft.Text("Um texto na página!"))
page.update()
# ou, o que é equivalente
page.add(ft.Text("Um texto na página!"))
insere o texto na View que está diretamente criada sobre page. View possui as seguintes propriedades e métodos.
View: Propriedades
Propriedade
Descrição
appbar
recebe um controle AppBar para exibir na parte superior da página.
auto_scroll
Booleano. True para que a barra de rolagem se mova para o final quando os filhos são atualizados. Para que scroll_to() funcione deve ser atribuído auto_scroll=False.
bgcolor
Cor de fundo da página.
controls
Lista de controles a serem inseridos na página. O último controle da lista pode se removido com page.controls.pop(); page.update().
fullscreen_dialog
Booleano. True se a página atual é um diálogo em tela cheia.
route
Rota da visualização (não usada atualmente). Pode ser usada para atualizar page.route em caso de nova visualização.
floating_action_button
Recebe um controle FloatingActionButton a ser exibido no alto da página.
horizontal_alignment
Alinhamento horizontal dos filhos. Default: horizontal_alignment=CrossAxisAlignment.START.
on_scroll_interval
Definição do intervalo de tempo para o evento on_scrollo, em milisegundos. Default: 10.
padding
Espaço entre o conteúdo do objeto e suas bordas, em pixeis. Default: 10.
scroll
Habilita rolagem (scroll) vertical para a página, evitando overflow. O valor da propriedade está em um ENUM ScrollMode com as possibilidades:
None (padrão): nenhum scroll. Pode haver overflow.
AUTO: scroll habilitado, a barra só aparece quando a rolagem é necessária.
ADAPTIVE: scroll habilitado, a barra de rolagem visível quando aplicativo é web ou desktop.
ALWAYS: scroll habilitado, a barra sempre exibida.
HIDDEN: scroll habilitado, barra de rolagem invisível.
spacing
Espaço vertical entre os controles da página, em pixeis. Default: 10. Só aplicado quando alignment = start, end, center.
vertical_alignment
Alinhamento vertical dos filhos. A propriedade está em um ENUM MainAxisAlignmente com as possibilidades:
Move a barra de scroll para uma posição absoluta ou salto relativo para chave especificada.
View: Evento
Evento
Descrição
on_scroll
Dispara quando a posição da barra de rolagem é alterada pelo usuário.
O controle View é útil em situações que se apresenta mais em uma visualização na mesma página e será visto mais tarde com maiores detalhes.
Flet: Container
Um objeto Container é basicamente um auxiliar de layout, um controle onde se pode inserir outros controles, permitindo a decoração de cor de fundo, borda, margem, alinhamento e preenchimento. Ele também pode responder a alguns eventos.
Como exemplo, o código abaixo:
import flet as ft
def main(page: ft.Page):
page.title = "Contêineres com cores de fundo"
def cor(e):
c4 = ft.Container(content=ft.Text("Outro conteiner azul!"), bgcolor=ft.colors.BLUE, padding=5)
page.add(c4)
c1 = ft.Container(content=ft.ElevatedButton("Um botão \"Elevated\""),
bgcolor=ft.colors.YELLOW, padding=5)
c2 = ft.Container(content=ft.ElevatedButton("Elevated Button com opacidade=0.5",
opacity=0.5), bgcolor=ft.colors.YELLOW, padding=5)
c3 = ft.Container(content=ft.Text("Coloca outra área azul"),
bgcolor=ft.colors.YELLOW, padding=5, on_click=cor)
page.add(c1, c2, c3)
ft.app(target=main)
gera a janela na figura 1, após 1 clique no botão c3.
O container c3 reage ao evento clique, adicionando um (ou mais) botão azul à janela.
Container: Propriedades
A figura 2 mostra o esquema de espaçamentos entre controles: a largura e altura (width, height) do container, a margem (margin) entre a caixa de decoração e o container, a borda (border) da caixa e o espaçamento interno (padding) entre o controle e a caixa.
Propriedade
Descrição
alignment
Alinhamento do controle filho dentro do Container para exibir na parte superior da página. Alignment é uma instância do objeto alignment.Alignment com propriedades x e y que representam a distância do centro de um retângulo.
x=0, y=0: o centro do retângulo,
x=-1, y=-1: parte superior esquerda do retângulo,
x=1.0, y=1.0: parte inferior direita do retângulo.
Constantes de alinhamento pré-definidas no módulo flet.alignment são: top_left, top_center, top_right, center_left, center, center_right, bottom_left, bottom_center, bottom_right. Por exemplo, mostrado na figura 4:
Ativa a animação predefinida do container, alterando valores de suas propriedades de modo gradual. O valor pode ser um dos seguintes tipos:
bool: True para ativar a animação do container com curva linear de duração de 1000 milisegundos.
int: ajusta tempo em milisegundos, com curva linear.
animation: Animation(duration: int, curve: str): ativa animação do container com duração e curva de transição especificadas.
Por exemplo:
import flet as ft
def main(page: ft.Page):
c = ft.Container(width=200, height=200, bgcolor="red", animate=ft.animation.Animation(1000, "bounceOut"))
def animar_container(e):
c.width = 100 if c.width == 200 else 200
c.height = 100 if c.height == 200 else 200
c.bgcolor = "blue" if c.bgcolor == "red" else "red"
c.update()
page.add(c, ft.ElevatedButton("Animate container", on_click=animar_container))
ft.app(target=main)
O código resulta na animação mostrada abaixo, na figura 5:
bgcolor
Cor de fundo do container.
blend_mode
modo de mistura (blend) de cores ou gradientes no fundo container.
blur
Aplica o efeito de desfoque (blur) gaussiano sobre o container.
O valor desta propriedade pode ser um dos seguintes:
um número: especifica o mesmo valor para sigmas horizontais e verticais, por exemplo 10.
uma tupla: especifica valores separados para sigmas horizontais e verticais, por exemplo (10, 1).
uma instância de ft.Blur: especifica valores para sigmas horizontais e verticais, bem como tile_mode para o filtro. tile_mode é o valor de ft.BlurTileMode tendo como default ft.BlurTileMode.CLAMP.
border
Borda desenhada em torno do controle e acima da cor de fundo. Bordas são descritas por uma instância de border.BorderSide, com as propriedades: width (número) e color (string). O valor da propriedade border é instância de border.Borderclasse, descrevendo os 4 lados do retângulo. Métodos auxiliares estão disponíveis para definir estilos de borda:
Permite especificar (opcional) o raio de arredondamento das bordas. O raio é instância de border_radius.BorderRadius com as propriedades: top_left, top_right, bottom_left, bottom_right. Esses valores podem ser passados no construtor da instância, ou por meio de métodos auxiliares:
Define a opacidade da imagem ao mesclar com um plano de fundo: valor entre 0.0 e 1.0.
image_repeat
Descrita junto com o objeto image.
image_src
Define imagem do plano de fundo.
image_src_base64
Define imagem codificada como string Base-64 como plano de fundo do container.
ink
True para efeito de ondulação quando o usuário clica no container. Default: False.
margin
Espaço vazio que envolve o controle. margin é uma instância de margin.Margin, definindo a propriedade para os 4 lados do retângulo: left, top, right e bottom. As propriedades podem ser dadas no construtor ou por meio de métodos auxiliares:
margin.all(value)
margin.symmetric(vertical, horizontal)
margin.only(left, top, right, bottom)
Por exemplo:
container_1.margin = margin.all(10)
container_2.margin = 20 # same as margin.all(20)
container_3.margin = margin.symmetric(vertical=10)
container_3.margin = margin.only(left=10)
padding
Espaço vazio de decoração entre borda do objeto e seu container. Padding é instância da padding.Padding com propriedades definidas como padding para todos os lados do retângulo: left, top, right e bottom. As propriedades podem ser dadas no construtor ou por meio de métodos auxiliares:
padding.all(value: float)
padding.symmetric(vertical, horizontal)
padding.only(left, top, right, bottom)
Por exemplo:
container_1.padding = ft.padding.all(10)
container_2.padding = 20 # same as ft.padding.all(20)
container_3.padding = ft.padding.symmetric(horizontal=10)
container_4.padding=padding.only(left=10)
shadow
Efeito de sombras projetadas pelo container. O valor dessa propriedade é uma instância ou uma lista de ft.BoxShadow, com as seguintes propriedades:
spread_radius: espalhamento, quanto a caixa será aumentada antes do desfoque. Default: 0.0.
blur_radius: desvio padrão da gaussiano de convolução da forma. Default: 0.0.
color: Cor da sombra.
offset: Uma instância de ft.Offsetclasse, deslocamentos da sombra, relativos à posição do elemento projetado. Os deslocamentos positivos em x e y deslocam a sombra para a direita e para baixo. Deslocamentos negativos deslocam para a esquerda e para cima. Os deslocamentos são relativos à posição do elemento que o está projetando. Default: ft.Offset(0,0).
blur_style: tipo de estilo, ft.BlurStyleque a ser usado na sombra. Default: ft.BlurStyle.NORMAL.
A forma do conteiner. O valor é ENUM BoxShape: RECTANGLE (padrão), CIRCLE
theme_mode
O ajuste do theme_mode redefine o tema usado no container e todos os objetos dentro dele. Se não for definido o tema em theme é válido para o container e seus filhos.
theme
Ajuste o tema global e dos filhos na árvore de controle.
Segue um exemplo de uso:
import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(color_scheme_seed=ft.colors.RED)
b1 = ft.ElevatedButton("Botão com tema da página")
b2 = ft.ElevatedButton("Botão com tema herdado")
b3= ft.ElevatedButton("Botão com tema dark")
c1 = ft.Container(
b1,
bgcolor=ft.colors.SURFACE_TINT,
padding=20,
width=300
)
c2 = ft.Container(
b2,
theme=ft.Theme(
color_scheme=ft.ColorScheme(primary=ft.colors.PINK)
),
bgcolor=ft.colors.SURFACE_VARIANT,
padding=20,
width=300
)
c3 = ft.Container(
b3,
theme=ft.Theme(
color_scheme_seed=ft.colors.INDIGO
),
theme_mode=ft.ThemeMode.DARK,
bgcolor=ft.colors.SURFACE_VARIANT,
padding=20,
width=300
)
page.add(c1, c2, c3)
ft.app(main)
O tema principal da página é definido em page.theme, usando um seed vermelho. Os botões b1 e b2 simnplesmente herdam o tema da página. O botão b3 está no container definido com theme_mode=ft.ThemeMode.DARK, exibindo o tema escuro. O código gera a janela mostrada na figura 6.
Vale lembrar que c1 = ft.Container(b1,...) é equivalente à c1 = ft.Container(content = b1,...) sendo que o content só pode ser omitido se o conteúdo for inserido como primeiro parâmetro.urlDefine a URL a ser abertta quando o container é clicado, disparando o evento on_click.url_target
Define onde abrir URL no modo web:
_blank (default): em nova janela ou aba,
_self: na mesma janela ou aba aberta.
Container: Eventos
Evento
Dispara quando
on_click
o usuário clica no container.
class ft.ContainerTapEvent():
local_x: float
local_y: float
global_x: float
global_y: float
Obs.: O objeto de evento e é uma instância de ContainerTapEvent, exceto se a propriedade ink = True. Nesse caso e será apenas ControlEvent com data vazio.
As propriedades e.local_x e e.local_y se referem à posição dentro do container c1, enquanto e.global_x e e.global_y se referem à posição global, dentro da página.
on_hover
o cursor do mouse entra ou abandona a área do container. A propriedade data do evento contém um string (não um booleano) e.data = "true" quando o cursor entra na área, e e.data = "false" quando ele sai.
Um exemplo de um container que altera sua cor de fundo quando o mouse corre sobre ele:
import flet as ft
def main(page: ft.Page):
def on_hover(e):
e.control.bgcolor = "blue" if e.data == "true" else "red"
e.control.update()
c1 = ft.Container(width=100, height=100, bgcolor="red",
ink=False, on_hover=on_hover)
page.add(c1)
ft.app(target=main)
on_long_press
quando o container recebe um click longo (pressionado por um certo tempo).
Detalhes sobre o gradiente de cores
O gradiente na cor de fundo admite como valor uma instância de uma das classes: LinearGradient, RadialGradient e SweepGradient.
A primeira imagem é gerada com LinearGradient, a segunda com RadialGradient e a última com SweepGradient.
São propriedades da classe LinearGradient:
begin
instância de Alignment. Posicionamento inicial (0.0) do gradiente.
end
instância de Alignment. Posicionamento final (1.0) do gradiente.
colors
cores assumidas pelo gradiente a cada parada. Essa lista deve ter o mesmo tamanho que stops se a lista for não nula. A lista deve ter pelo menos duas cores.
stops
lista de valores de 0.0 a 1.0 marcando posições ao longo do gradiente. Se não nula essa lista deve ter o mesmo comprimento que colors. Se o primeiro valor não for 0.0 fica implícita uma parada em 0,0 com cor igual à primeira cor em colors. Se o último valor não for 1.0 fica implícita uma parada em 1.0 e uma cor igual à última cor em colors.
tile_mode
como o gradiente deve preencher (tile) a região antes de begin depois de end. O valor é um ENUM GradientTileMode com valores: CLAMP (padrão), DECAL, MIRROR, REPEATED.
rotation
rotação do gradiente em radianos, em torno do ponto central de sua caixa container.
Mais Informações:
Gradiente linear na documentação do Flutter.
Unidade de medida de radianos na Wikipedia.
São propriedades da classe RadialGradient:
colors, stops, tile_mode, rotation
propriedades idênticas às de LinearGradient.
center
instância de Alignment. O centro do gradiente em relação ao objeto que recebe o gradiente. Por exemplo, alinhamento de (0.0, 0.0) coloca o centro do gradiente radial no centro da caixa.
radius
raio do gradiente, dado como fração do lado mais curto da caixa. Supondo uma caixa com largura = 100 e altura = 200 pixeis, um raio de 1 no gradiente radial colocará uma parada de 1,0 afastado 100,0 pixeis do centro.
focal
ponto focal do gradiente. Se especificado, o gradiente parecerá focado ao longo do vetor do centro até esse ponto focal.
focal_radius
raio do ponto focal do gradiente, dado como fração do lado mais curto da caixa. Ex.: um gradiente radial desenhado sobre uma caixa com largura = 100,0 e altura = 200,0 (pixeis), um raio de 1,0 colocará uma parada de 1,0 a 100,0 pixels do ponto focal.
São propriedades da classe SweepGradient:
colors, stops, tile_mode, rotation
propriedades idênticas às de LinearGradient.
center
centro do gradiente em relação ao objeto que recebe o gradiente. Por exemplo, alinhamento de (0.0, 0.0) coloca o centro do gradiente no centro da caixa.
start_angle
ângulo em radianos onde será colocada a parada 0.0 do gradiente. Default: 0.0.
end_angle
ângulo em radianos onde será colocada a parada 1.0 do gradiente. Default: math.pi * 2.
Graus e radianos
A maiora das medidas angulares na programação do flet (e do python em geral) é dada em radianos. Segue uma breve imagem explicativa do uso de radianos.
Estritamente dizendo deveríamos iniciar nosso estudo do Flet considerando os objetos mais básicos, no sentido de serem containeres de outros objetos. No entanto já vimos vários casos de pequenos aplicativos que usam botões. É difícil sequer exibir exemplos de código de GUI sem botões. Por isso vamos descrever aqui o uso dos botões, deixando para depois uma descrição dos controles de layout.
Alguns controles tem a função principal de obter informações do usuário, como botões, menus dropdown ou caixas de texto, para inserção de dados. Outros servem para a exibição de informações calculadas pelo código, mostrando gráficos, figuras ou textos. As caixas de textos podem ser usadas em ambos os casos.
Buttons (botões)
Os seguintes tipos de botões estão disponíveis (e ilustrados na figura 1), contendo as propriedades, métodos e respondendo a eventos:
Vamos usar alguns exemplos para ilustrar as propriedades e métodos desses objetos.
import flet as ft
def main(page: ft.Page):
def mudar(e):
bt2.disabled = not bt2.disabled
bt2.text = "Desabilitado" if bt2.disabled else "Habilitado"
page.update()
bt1 = ft.FilledButton(text="FilledButton", on_click=mudar, width=200)
bt2 = ft.ElevatedButton("Habilitado", disabled=False, width=200)
page.add(bt1, bt2)
ft.app(target=main)
Nesse caso um FilledButton aciona a função mudar que alterna a propriedade disabled do botão elevado. Observe que um botão com disabled=True não reage ao clique, nem à nenhum outro evento. O operador ternário foi usado: valor_se_true if condicao else valor_se_false.
Dois novos eventos são mostrados a seguir: on_hover, que é disparado quando o cursor se move sobre o botão (sem necessidade de clicar) e on_long_press, disparado com um clique longo. Um ícone é inserido nos botões ElevatedButton, cujas cores são alteradas pelos eventos descritos.
Os botões assumem os estados mostrados na figura 2.
Uma grande quantidade de ícones está disponível e pode ser pesquisada em Flet.dev: Icons Browser.
Botões possuem a propriedade data que pode armazenar um objeto a ser passado para as funções acionadas por eventos. As propriedades dos widgets funcionam como variáveis globais. No exemplo abaixo temos uma caixa de texto, que exibe a propriedade data. O botão elevado também tem uma propridade data que não é exibida mas serve para armazenar quantas vezes o botão foi clicado. Ele serve de container para um Row contendo 3 ícones (ft.Icon(ft.icons.NOME_DO_ICONE)).
import flet as ft
def main(page: ft.Page):
def bt_clicou(e):
bt.data += 1
txt.value = f"O botão foi clicado {bt.data} {'vezes' if bt.data >1 else 'vêz'}"
page.update()
txt = ft.Text("O botão não foi clicado", size=25, color="navyblue", italic=True)
bt = ft.ElevatedButton("Clica!",
content=ft.Row(
[
ft.Icon(ft.icons.CHAIR, color="red"),
ft.Icon(ft.icons.COTTAGE, color="green"),
ft.Icon(ft.icons.AGRICULTURE, color="blue"),
], alignment=ft.MainAxisAlignment.SPACE_AROUND,),
on_click=bt_clicou, data=0, width=150,
)
page.add(txt, bt)
ft.app(target=main)
Se o nome do ícone não for fornecido como primeiro parâmetro o nome do parâmetro deve ser nomeado: ft.Icon(color="red", name=ft.icons.CHAIR). E execução do código resulta nas janelas exibidas na figura 3.
As caixas de texto podem receber formatações diversas como size (tamanho da fonte), color (cor da fonte) bgcolor (cor do fundo), italic (itálico), weight (peso da fonte). A propriedade selectable informa se o texto exibido pode ser selecionado, e estilos diversos podem ser atribuídos em style. A página recebe a propriedade page.scroll = "adaptive" para que possa apresentar uma barra de scroll.
O resultado é mostrado na figura 4. A janela foi dimensionada para ser menor que o texto existente, mostrando a barra de scroll.
O número máximo de linhas exibidas, max_lines, ou largura e altura do texto, width e height são úteis quando se apresenta texto logo em uma janela.
import flet as ft
def main(page: ft.Page):
page.scroll = "adaptive"
texto1 = (
"René Descartes (La Haye en Touraine, 31 de março de 1596, Estocolmo, 11 de"
"fevereiro de 1650) foi um filósofo, físico e matemático francês. Durante a"
"Idade Moderna, também era conhecido por seu nome latino Renatus Cartesius."
)
texto2 = (
"Descartes, por vezes chamado de o fundador da filosofia moderna e o pai da"
"matemática moderna, é considerado um dos pensadores mais importantes e"
"influentes da História do Pensamento Ocidental. Inspirou contemporâneos e"
"várias gerações de filósofos posteriores; boa parte da filosofia escrita "
"a partir de então foi uma reação às suas obras ou a autores supostamente"
"influenciados por ele. Muitos especialistas afirmam que, a partir de"
"Descartes, inaugurou-se o racionalismo da Idade Moderna."
)
t7 = ft.Text("Limita texto longo a 1 linha, com elipses", style=ft.TextThemeStyle.HEADLINE_SMALL)
t8 = ft.Text(texto1, max_lines=1, overflow="ellipsis")
t9 = ft.Text("Limita texto longo a 2 linhas", style=ft.TextThemeStyle.HEADLINE_SMALL)
t10 = ft.Text(texto2, max_lines=2)
t11 = ft.Text("Limita largura e altura do texto longo", style=ft.TextThemeStyle.HEADLINE_SMALL)
t12 = ft.Text(texto2, width=700, height=100)
page.add(t7, t8, t9, t10, t11, t12)
ft.app(target=main)
Resultando na janela mostrada na figura 5. O parâmetro overflow="ellipsis" mostra uma elipses onde on texto foi quebrado. As definições de texto1 e texto2 correspondem a uma das formas de declarar strings longas no python.
Existem estilos pré-definidos. código abaixo usamos o texto do widget igual ao nome do estilo, para facilitar a visualização: style=ft.TextThemeStyle.NOME_DO_ESTILO. Apenas as definições estão mostradas e o resultado está na figura 6.
Propriedades dos controles pode ser alterados dinamicamente por meio de inputs recebidos do usuário (ou outra origem qualquer). No próximo exemplo o tamanho da fonte é controlado por um flet.Slider.
import flet as ft
def main(page: ft.Page):
def font_size(e):
t.size = int(sl.value)
t.value = f"Texto escrito com fonte Bookerly, size={t.size}"
t.update()
t = ft.Text("Texto escrito com fonte Bookerly, size=10", size=10, font_family="Bookerly")
sl = ft.Slider(min=0, max=100, divisions=10, value=10, label="{value}", width=500, on_change=font_size)
page.add(t, sl)
ft.app(target=main)
O app gerado está na figura 7. Observe que a propriedade do Slider.label="value" exibe acima do cursor (como um tooltip) o valor do controle. O tamanho da fonte é ajustado de acordo com esse valor.
Se a fonte está instalada localmente basta usar font_family="Nome_da_Fonte". Para fontes remotas podemos definir uma ou várias fontes a serem usadas. page.fonts recebe um dicionário com nomes e locais das fontes.
page.fonts = {
"Kanit": "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf",
"Open Sans": "fonts/OpenSans-Regular.ttf",
}
page.theme = Theme(font_family="Kanit")
page.add(
ft.Text("Esse texto é renderizado com fonte Kanit"),
ft.Text("Esse texto é renderizado com fonte 'Open Sans'", font_family="Open Sans"),
Ícones
O objeto fleet.Icon pode ser inserido em vários conteineres. Ele possui as propriedades (entre outras) color, name, size e tooltip. O tamanho default é size=24 enquanto tooltip é o texto exibido quando o cursor está sobre o ícone.
O código ilustra esse uso:
O nome do ícone pode ser dado como name=ft.icons.ZOOM_IN ou uma string name="child_care", onde o nome pode ser pesquisado no Icons Browser. Note que ft.icons contém ENUMS predefinidos e name=ft.icons.AIR_SHARP é o mesmo que name="air_sharp".
Ícones personalizados podem ser inseridos como imagens, como em flet.Image(src=f"img/Search.png"), onde o caminho pode ser absoluto ou relativo, em referência ao diretório onde está o aplicativo. Isso significa que a imagem da lupa exibida na figura está armazenada em pasta_do_aplicativo/img/Search.png.
Vimos no primeiro artigo dessa série que um aplicativo Python com Flet consiste em código Python para a realização da lógica do aplicativo, usando o Flet como camada de exibição. Mais tarde nos preocuparemos em fazer uma separação explícita das camadas. Por enquanto basta notar que o Flet cria uma árvore de widgets cujas propriedades são controladas pelo código. Widgets também podem estar associados à ações ligadas a funções. Portanto, para construir aplicativos com Flet, precisamos conhecer esses widgets, suas propriedades e eventos que respondem.
Alguns controles tem a função principal de obter informações do usuário, como botões, menus dropdown ou caixas de texto, para inserção de dados. Outros servem para a exibição de informações calculadas pelo código, mostrando gráficos, figuras ou textos. As caixas de textos podem ser usadas em ambos os casos.
A interface do Flet é montada como uma composição de controles, arranjados em uma hierarquia sob forma de árvore que se inicia com uma Page. Dentro dela são dispostos os demais controles, sendo que alguns deles são também conteineres, abrigando outros controles. Todos os controles, portanto, possuem um pai, exceto a Page. Controles como Column, Row e Dropdown podem conter controles filhos, como mostrado na figura 10.
Categorias de Controles
🔘 Controles
Categoria
Itens
🔘 Layout
diagramação
16 itens
🔘 Navigation
navegação
3 itens
🔘 Information Displays
exibição de informação
8 itens
🔘 Buttons
botões
8 itens
🔘 Input and Selections
entrada e seleções
6 itens
🔘 Dialogs, Alerts, Panels
dialogo e paineis
4 itens
🔘 Charts
gráficos
5 itens
🔘 Animations
animações
1 item
🔘 Utility
utilidades
13 itens
Propriedades comuns a vários controles
Algumas propriedades são comuns a todos (ou quase todos) os controles. Vamos primeiro listá-las e depois apresentar alguns exemplos de uso. As propriedades marcadas com o sinal ≜ só são válidas quando estão dentro de uma controle Stack, que será descrito após a tabela.
bottom
≜
Distância entre a borda inferior do filho e a borda inferior do Stack.
data
Um campo auxiliar de dados arbitrários que podem ser armazenados junto a um controle.
disabled
Desabilitação do controle. Por padrão disabled = False. Um controle desabilitado fica sombreado e não reage a nenhum evento. Todos os filhos de um controle desabilitado ficam desabilitados.
expand
Um controle dentro de uma coluna ou linha pode ser expandido para preencher o espaço disponível. expand=valor, onde valor pode ser booleano ou inteiro, um fator de expansão, usado para dividir o espaço entre vários controles.
hight
Altura do controle, em pixeis.
left
≜
Distância da borda esquerda do filho à borda esquerda do Stack.
right
≜
Distância da borda direita do filho à borda direita do Stack.
top
≜
Distância da borda superior do filho ao topo do Stack.
visible
Visibilidade do controle e seus filhos. vivible = True por padrão. Controles invisíveis não recebem foco, não podem ser selecionados nem respondem a eventos.
widht
Largura de controle em pixeis.
Um Stack é um controle usado para posicionar controles em cima de outros (empilhados). Veremos mais sobre ele na seção sobre layouts.
Transformações (Transformations)
Transformações são operações realizadas sobre os controles
offset
É uma translação aplicada sobre um controle, antes que ele seja renderizado. A translação é dada em uma escala relativa ao tamanho do controle. Um deslocamento de 0,25 realizará uma translação horizontal de 1/4 da largura do controle. Ex,: ft.Container(..., offset=ft.transform.Offset(-1, -1).
opacity
Torna o controle parcialmente transparente. O default é opacity=1.0, nenhuma transparência. Se opacity=0.0controle é 100% transparente.
rotate
Aplica uma rotação no controle em torno de seu centro. O parâmetro rotate pode receber um número, que é interpretado com o ângulo, em radianos, de rotação anti-horária. A rotação também pode ser especificada por meio de transform.Rotate, que permite estabelecer ângulo, alinhamento e posição de centro de rotação. Ex,: ft.Image(..., rotate=Rotate(angle=0.25 * pi, alignment=ft.alignment.center_left) representa uma rotação de 45° (π/4).
scale
Controla a escala ao longo do plano 2D. O fator de escala padrão é 1,0. Por ex.: ft.Image(..., scale=Scale(scale_x=2, scale_y=0.5) multiplica as dimensões em x por 2 e em y por .5. Alternativamente Scale(scale=3) multiplica por 3 nas duas direções.
bt6 ⇾ torna a cor mais translúcida (ct.opacity -= .1),
bt7 ⇾ retorna a imagem para o estado inicial,
onde ct = ft.Container, é o container de cor vermelha, mostrado no figura 12.
Atalhos de Teclado
Qualquer pessoa que faz uso intensivo do computador sabe da importância dos Atalhos de Teclado (Keyboard shortcuts). A possibilidade de não mover a mão do teclado para acionar o mouse pode significar melhor usabilidade e aumento de produtividade. Para isso o Flet oferece para o programador a possibilidade de inserir atalhos em seus aplicativos para que seu usuário possa dinamizar seu uso.
Para capturar eventos produzidos pelo teclado o objeto page implementa o método on_keyboard_event, que gerencia o pressionamento de teclas de caracter, em combinação com teclas de controle. Esse evento passa o parâmetro eque é uma instância da classe KeyboardEvent, e tem as seguintes propriedades:
e.key
Representação textual da tecla pressionada. Veja nota†.
e.shift
Booleano: True se a tecla “Shift” foi pressionada.
e.ctrl
Booleano: True se a tecla “Control” foi pressionada.
e.alt
Booleano: True se a tecla “Alt” foi pressionada.
e.meta
Booleano: True se a tecla “Meta” foi pressionada††.
Nota†: Além dos caracteres A … Z (todos apresentados em maiúsculas) também são representadas as teclas Enter, Backspace, F1 … F12, Escape, Insert … Page Down, Pause, etc. Alguns teclados permitem a reconfiguração de teclas, por exemplo fazendo F1 = Help, etc. Nota††: A tecla Meta é representada em geral no Windows como tecla Windows e no Mac como tecla Command.
O seguinte código ilustra esse uso. A linha page.on_keyboard_event = on_teclado faz com que eventos de teclado acionem a função on_teclado. O objeto e leva as propriedades booleanas e.ctrl, e.alt, e.shift, e.meta e o texto e.key.
O resultado desse código, quando executado e após o pressionamento simultaneo das teclas CTRL-ALT-SHIFT-J, está mostrado na figura 13.
O exemplo acima ilustra ainda uma característica da POO (programação orientada a objetos). Como temos que criar 5 caixas de texto iguais, exceto pelo seu valor, criamos uma classe BtControle (herdando de ft.TextField) e criamos cada botão como instância dessa classe. No código manipulamos a visibilidade desses botões.
Já vimos em Templates do Django que os templates são modelos usados na arquitetura MTV para renderizar as requisições do cliente e gerar texto com marcação HTML. Dentro desses modelos podemos receber variáveis enviadas no contexto, com a marcação {{ var }}. Um contexto é um conjunto de valores, em um objeto tipo dicionário (um conjunto de chaves:valores), passado pelas views para o template. Os nomes das variáveis são passados como strings.
# se temos a variável
» contexto = {"n1": "um"; "n2":"dois";}
# e o template
⎀ Temos no contexto {{ n1 }} e {{ n2 }}.
# será renderizado como
↳ Temos no contexto um e dois.
Filtros são formas de modificar as saídas de variáveis. Por ex.:
» contexto = {'django': 'o framework web para perfecionistas com prazos'}
⎀ {{ django|title }}
↳ O Framework Web Para Perfecionistas Com Prazos
# ou
» contexto = {'data': '2022-07-04'}
⎀ {{ data|date:"d/m/Y" }}
↳ 04/07/2022
Muitos outros filtros são predefinidos e outros mais podem ser definidos pelo programador.
Filtros do Django
add soma valor ao argumento. Tenta forçar a conversão de strings para numéricos.
Alguns formatos são pre-definidos. Veja a lista completa em Django docs.
Caracter
Descrição
Exemplo
Dia
d
dia do mes com dois dígitos
’01’ até ’31’
j
dia do mes sem zeros.
‘1’ até ’31’
D
dia da semana em texto, 3 letras.
‘Fri’
l
dia da semana em texto completo.
‘Friday’
w
dia da semana, numérico.
‘0’ (Domingo) até ‘6’ (Sábado)
z
dia do ano.
1 to 366
Semana
W
número da semana no ano.
1, 53
Mês
m
mês, 2 dígitos.
’01’ até ’12’
n
mês sem zeros.
‘1’ até ’12’
M
mês, texto, 3 letras.
‘Jan’, ‘Dec’
b
mês, texto, 3 letras, ninúsculas.
‘jan’, ‘dec’
F
mês, texto, extenso.
‘January’
t
quantos dias no mês.
28 to 31
Ano
y
ano, 2 dígitos.
’00’ até ’99’
Y
ano, 4 dígitos.
‘0001’, …, ‘1999’, …, ‘9999’
L
Booleano, se ano é bissexto.
True ou False
Hora
g
hora, formato 12-hora sem zeros.
‘1’ até ’12’
G
hora, formato 24-hora sem zeros.
‘0’ até ’23’
h
hora, formato 12-hora.
’01’ até ’12’
H
hora, formato 24-hora.
’00’ até ’23’
i
minutos.
’00’ até ’59’
s
segundos, 2 dígitos.
’00’ até ’59’
u
microssegundos.
000000 até 999999
a
‘a.m.’ ou ‘p.m.’
A
‘AM’ ou ‘PM’.
f
hora, format 12-horas e minutos
‘1:30’
P
hora, formato 12-hora horas, minutos
‘a.m.’/’p.m.’
Timezone
e
nome da timezone
‘GMT’, ‘-500’, ‘US/Eastern’, etc.
O formato pode ser um dos predefinidos como DATE_FORMAT, DATETIME_FORMAT, SHORT_DATE_FORMAT ou SHORT_DATETIME_FORMAT ou um formato construído com os especificadores acima. Formatos predefinidos podem depender do ajuste local.
# se data_valor é um objeto datetime, como o resultante de datetime.datetime.now()
# com hora 23:45, dia 01/11/2021
⎀ {{ data_valor|date:"D d M Y" }} {{ data_valor|time:"H:i" }}
↳ Mon 01 Nov 2021 23:45
# se o locale for pt-BR
⎀ {{ data_valor|date:"SHORT_DATE_FORMAT" }}
↳ 01/11/2021
default, fornece valor default se argumento for False.
# se livros é
» livros = [
» {'titulo': '1984', 'autor': {'nome': 'George', 'idade': 45}},
» {'titulo': 'Timequake', 'autor': {'nome': 'Kurt', 'idade': 75}},
» {'titulo': 'Alice', 'autor': {'nome': 'Lewis', 'idade': 33}},
» ]
# o código em template
⎀ {% for livro in livros|dictsort:"autor.idade" %}
⎀ * {{ livro.titulo }} ({{ livro.autor.nome }})
⎀ {% endfor %}
# resultaria em
↳ * Alice (Lewis)
↳ * 1984 (George)
↳ * Timequake (Kurt)
dictsortreversed tem o mesmo efeito que dictsort, mas ordenando em ordem invertida.
divisibleby returna True se o valor é divisível pelo argumento.
» valor = 171
⎀ {{ value|divisibleby:"3" }}
↳ True
escape promove remoção de tags html.
# esse exemplo mostra a exibição final no navegador
» string_html = "<b>Negrito<b>"
⎀ {{ string_html }}
↳ <b>Negrito<b>
⎀ {{ string_html|scape }}
↳ <b>Negrito</b>
escape converte:
< em <
> em >
' (aspas simples) em '
" (aspas duplas) em "
& em &
first retorna o 1º elemento de uma lista.
» lista = ["casa","da","sogra"]
⎀ {{ lista|first }}
↳ casa
floatformat promove o arredondamento de números flutuantes.
» valor = 34.23234
⎀ {{ valor|floatformat }}
↳ 34.2
» valor = 34.0000
⎀ {{ valor|floatformat }}
↳ 34
» valor = 34.26000
⎀ {{ valor|floatformat }}
↳ 34.3
O número de casas pode ser definido em valor|floatformat:n. Passando “0” como argumento o arredondamento será para o inteiro mais próximo. O sufixo g introduz separador de milhar, definido em THOUSAND_SEPARATOR.
valor
template
output
34.23234
{{ valor|floatformat:2 }}
34.23
34.00000
{{ valor|floatformat:2 }}
34.00
34.26000
{{ valor|floatformat:2 }}
34.26
34.23234
{{ valor|floatformat:”0″ }}
34
31.00000
{{ valor|floatformat:”0″ }}
31
39.56000
{{ valor|floatformat:”0″ }}
40
34232.34
{{ valor|floatformat:”2g” }}
34,232.34
34232.06
{{ valor|floatformat:”g” }}
34,232.1
get_digit retorna um inteiro na posição especificada, contando do final para o início. Se não for possível encontar esse dígito, retorna o valor original.
pluralize retorna sufixo para plurais se valor do parâmetro for maior que 1. Esse valor pode ser o comprimento do objeto. O sufixo default é s, mas isso pode ser alterado.
» itens_compra = 1
⎀ Você tem que comprar {{ itens_compra }} objeto{{ itens_compra|pluralize }}.
↳ Você tem que comprar 1 objeto.
» itens_compra = 23
⎀ Você tem que comprar {{ itens_compra }} objeto{{ itens_compra|pluralize }}.
↳ Você tem que comprar 23 objetos.
Sufixos alternativos podem ser inseridos como parâmetros:
» quantos = 1
⎀ Você fez o pedido de {{ quantos }} paste{{ quantos|pluralize:"l,is" }}.
↳ Você fez o pedido de 1 pastel.
» quantos = 45
⎀ Você fez o pedido de {{ quantos }} paste{{ quantos|pluralize:"l,is" }}.
↳ Você fez o pedido de 45 pasteis.
random retorna um elemento aleatório de uma lista.
» lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|random }}
# um possível resultado é
↳ mãe
rjust alinha texto à direita dentro de espaço de n caracteres dado.
slugify converte texto em ASCII puro, convertendo espaços em hífens. Remove caracteres que não são alfanuméricos, sublinhados (underscores) ou hífens. Converte tudo para minúsculas eliminando espaços nas bordas.
striptags remove tags [X]Html sempre que possível.
» valor = "<b>Um texto pode ter</b> <button>várias tags</button> <span>(x)html</span>!"
⎀ {{ valor|striptags }}
↳ Um texto pode ter várias tags (x)html!
Observação:striptags não garante que o texto seja seguro. Não aplique a filtro safe sobre o resultado de striptags.
time formata variável tipo time de acordo com formato especificado.
No exemplo caracteres literais foram escapados (\h, \m).
timesince formata uma diferença de datas entre now (agora) e data fornecida em parâmetro.
# se artigo_gravado contem uma data e
» hora = datetime.datetime.now()
⎀ {{ artigo_gravado|timesince:hora }}
↳ 14 days, 18 hours
timeuntil é análoga à timesince mas retornando a diferença entre uma data data e data futura.
title formata string como título de artigos e livros, colocando em maiúsculas as primeiras letras de cada palavra.
» titulo = "análise auxiliar de enrolação científica"
⎀ {{ titulo|title }}
↳ Análise Auxiliar De Enrolação Científica
truncatechars realiza o truncamento de um texto em um número especificado de caracteres. O texto truncado é seguido de elipses ….
» texto = "Esta é uma nota grande."
⎀ {{ texto|truncatechars:15 }}
↳ Esta é uma nota...
# nada é feito de o texto for menor que o parâmetro de truncamento
⎀ {{ texto|truncatechars:35 }}
↳ Esta é uma nota grande.
truncatechars_html é similar à truncatechars mas evitando o descarte de tags html.
» texto = "
Esta é uma nota grande.
"
⎀ {{ texto|truncatechars_html:15 }}
↳ <p>Esta é uma not...</p>
truncatewords trunca uma string após um número dado de palavras. O texto truncado é seguido de elipses …. Quebras de linha são removidas.
» texto = "Um texto com\n muitas palavras pode ser cortado"
⎀ {{ texto|truncatewords:4 }}
↳ Um texto com muitas...
truncatewords_html é similar à truncatewords, mas evitando eliminar tags html. Quebras de linha são mantidas.
» texto = "<p>Um texto com\n muitas palavras pode ser cortado</p>"
⎀ {{ texto|truncatewords:4 }}
↳ <p>Um texto coman muitas...</p>
unordered_list constroi uma lista html à partir de listas e listas aninhadas, inserindo recursivamente sublistas quando necessário.
urlize converte uma URL ou endereço de email em links clicáveis.
» url = "Visite minha página em phylos.net"
⎀ {{ url|urlize }}
↳ Visite minha página em phylos.net
# emails também são convertidos
» email = "Mande sua mensagem para usuario@exemplo.com"
⎀ {{ email|urlize }}
↳ Mande sua mensagem para usuario@exemplo.com
O atributo rel=”nofollow” é acrescentado.
urlizetrunc age como urlize mas truncando urls longas para a exibição>
{{ url|urlizetrunc:15 }}
wordcount retorna número de palavras no parâmetro.
» texto = "Um texto com algumas palavras."
⎀ {{ texto|wordcount }}
↳ 5
wordwrap quebra o texto em comprimento especificado, inserindo quebra de linhas. wordwrap:n não quebra palavras mas sim a linha em valores inferiores a n dado como comprimento.
Implements word wrapping by inserting a newline character every n characters. Useful for plain text, but not typically for HTML.
» texto = "Um texto com algumas palavras."
⎀ {{ texto|wordwrap:17 }}
↳ Um texto com
↳ algumas palavras.
yesno retorna uma string especificada para o valor do parâmetro True, False ou None (opcional).
Django foi desenvolvido como um projeto interno no jornal Lawrence Journal-World em 2003 para atender à necessidade de implementar novos recursos com muito pouco prazo, e tornado disponível publicamente em julho de 2005. Ele é mantido pela Django Software Foundation (DSF), uma organização independente estabelecida nos EUA como uma organização sem fins lucrativos. Alguns sites conhecidos que usam Django incluem Instagram, Spotify, YouTube, Mozilla, Disqus, The Washington Post, Dropbox e muitos outros.
Django é um framework web gratuito, de código aberto e baseado em Python. Seu principal objetivo é a construção de sites complexos baseados em banco de dados, de forma rápida e de fácil manutenção. Sua estrutura prioriza a reutilização de componentes, usando menos código e o princípio DRY (não se repita). O Python é usado extensivamente na configuração, no acesso aos bancos de dados e na camada de exibição.
Como framework o Django é completo (diferente do Flask), podendo ser usado sem a adição de pacotes adicionais, embora possa receber plugins para incrementar sua funcionalidade. O Django fornece uma interface de administração (um painel do usuário) opcional gerada dinamicamente por introspecção que possibilita as operações de CRUD no banco de dados e tem suporte para bancos de dados SQLite, PostgresSQL e MySQL (e outros).
Arquitetura MTV
Django segue o padrão da arquitetura MTV, modelo–template–visualização (model–template–views).
Model: os dados a serem apresentados pelo aplicativo. Geralmente lidos em um banco de dados.
View: um gerenciador de requisições que seleciona o template apropriado.
Template: um arquivo básico (com estrutura HTML) contendo o layout da página web com marcadores para preenchimento dos dados requisitados.
Um quadro pode ajudar a esclarecer o modelo.
Descrição do modelo MVT
(Leia esse quadro e retorne a ele mais tarde, depois de ter lidos sobre as várias camadas do django.
O navegador envia uma requisição para o servidor rodando django (1),
a URL é recebida por urls.py que atribui uma view para tratamento da requisição,
a camada view consulta Model (2) para receber os dados requisitados e recebe dela esses dados (3),
depois de obter os dados View consulta a camada Template (4) para formatar a apresentação final (5) e
envia páginas html formatadas para o navegador cliente (6).
Sobre aprender Django
Para se obter um entendimento razoável do Django é necessário ter alguns pre-requisitos, que não são cobertos nessas notas. Primeiro é necessário entender como as páginas na web são formadas com html e formatadas com css. Um conhecimento do Python também é essencial, em particular sobre estruturas de dados: uso de listas e tuplas, dicionários e, principalmente, o uso de programação orientada a objetos.
Esses artigos adotam a abordagem de cobrir os aspectos básicos do django para dar uma visão geral do processo de criação e manutenção de sites e aplicativos web. Após uma leitura desse texto e a experimentação com o código proposto a consulta à documentação oficial do django deverá ser compreensível para o leitor.
Instalações
Para usar essas instruções o ideal seria ter uma instalação das últimas versões do Python e do Django. Usaremos o banco de dados SQlite que não necessita nenunha instalação especial. Também poderiam ser usados o MySQL, o PostgreeSQL ou vários outros bancos de dados.
Embora não obrigatório é sempre bom trabalhar em uma área isolada usando um ambiente virtual. Instruções sobre ambientes virtuais podem ser lidas aqui: Ambientes Virtuais, pip e conda. Para isso crie um diretório destinado a conter o projeto do django, e um ambiente virtual:
$ mkdir ~/Projetos/django
$ cd ~/Projetos/django
$ python3.10 -m venv env
# para usar o ambiente virtual
$ source env/bin/activate
# o prompt de comando muda para
(env) $
# para desativar o ambiente virtual (quando for o caso)
(env)$ deactivate
As linhas de código acima criam o diretório ~/Projetos/django (lembrando que no linux ~ representa a pasta do usuário). No Windows os comandos devem ser alterados de acordo com a sintaxe do sistema. Criando um ambiente virtual alguns diretórios específicos (bin, include, lib) são gravados com uma cópia da instalação do Python, e algumas variáveis de ambiente são redefinidas. Pacotes instalados com o pip (ou outro gerenciador) serão colocados nesse ambiente.
Estando dentro do ambiente virtual, instalamos a última versão do django (que era a 4.0.5 em junho de 2022) usamos:
(env) $ pip install Django==4.0.5
# para verificar a instalação
(env) $ python -m django --version
4.0.5
Prosseguiremos com a construção de um projeto em django no artigo 2- Django, um Projeto.
Artigos Django
1. Django, Websites com Python (esse artigo): Introdução, instalação.
2. Um Projeto no Django: Criação e gerenciamento de projetos, criação de apps, templates, herança de templates, arquivos Estáticos e CSS, Modelos de dados, admin, exibição de dados.
Um aplicativo web (ou outro qualquer) deve ser escrito de forma clara, para facilitar sua expansão e manutenção. Uma das formas usadas pelo Flask para implementar esse estratégia é a de colocar código python e html separados. Os templates, como vimos, são modelos ou estruturas básicas que podem ser preenchidas dinamicamente, de acordo com as requisições. Esse é o chamado de modelo de separação entre lógica de negócio e lógica de exibição (business and presentation logic). Templates são tratados por um dos módulos que compõem o Flask: o módulo Jinja2.
Um exemplo básico de template para a exibição de um artigo poderia ser o seguinte:
# template.html
<h1>{{ titulo }} </h1>
<p>{{ autor }}, {{ data }} </p>
<p>{{ texto_do_artigo }} </p>
<p>{{ pe_de_pagina }} </p>
Os campos {{ variavel }} são chamados de localizadores (placeholders) para os valores que serão passados pelas funções view. Em muitos casos as informações usadas para popular essas variáveis são lidas em um banco de dados.
onde /frutas/<nome_da_fruta>, fornece o valor da varíavel passada para o parâmetro da função (em vermelho). Dentro do corpo da função a variável de mesmo nome (em verde) recebe esse valor. Esses nomes não precisam ser os mesmo, embora esse seja uma prática comum entre programadores do python.
O método render_template() é parte do Jinja2 para integrar templates e lógica do aplicativo.
Filtros
Variáveis e objetos do python podem ser integrados nos templates de algumas formas. Por meio do módulo Jinja temos diversos filtros para manipular campos em templates. Já vimos como inserir uma variável em um template. Um exemplo de filtro é title(), que torna a string no formado de título, com a primeira letra de cada palavra em maísculo.
# suponha que temos a variável titulo = "a casa da mãe joana"
# essa string pode ser exibida dentro de uma tag <h1>
<h1> {{ titulo }} </h1>
↳ a casa da mãe joana
# para maísculas na primeira letra de cada palavra
<h1> {{ titulo | title() }} </h1>
↳ A Casa Da Mãe Joana
Uma descrição dos filtros para texto está na tabela abaixo.
Filtro
Descrição
capitalize
Converte 1º caracter em maiúsculo, os demais em minúsculo
lower
Converte todos os caracteres minúsculo
upper
Converte todos os caracteres maiúsculo
title
Converte 1º caracter de cada palavra em maiúsculo
trim
Remove espaços em branco no início e no fim
safe
Renderiza o valor sem aplicar escape (inclui tags)
striptags
Remove todas as tags HTML do valor
safe: O filtro safe informa ao Flask que a tag html pode ser renderizada com segurança. Mais exemplos abaixo. striptags: remove as tags <tag> e </tag> e retorna o texto puro.
Exemplos de Filtros em Strings
Suponha que temos uma variável de nome titulo. Nos templates ela pode ser exibida diretamente, como uma string, ou passando por algum dos vários filtros. Nos quadros seguintes os comentários são iniciados por # enquanto outputs são identificados pelo sinal ↳ .
# suponha que a variável titulo2 não está definida
# default fornece um valor default (se titulo2 não está definido).
<h1> {{titulo2 | default ("Título Não Encontrado")}} </h1>
↳ Título Não Encontrado
# torna maiúscula a primeira letra
<h1> {{"mercado" | capitalize()}} </h1>
↳ Mercado
# em linha anterior ao uso podemos definir um valor
# capitalize torna maiúscula a 1ª letra de cada palavra
{% set titulo2 = "um título para a página" %}
<h1> {{ titulo2 | capitalize()}} </h1>
↳ Um título para a página
# title() torna maiúscula a 1ª letra de cada palavra
<h1> {{titulo2 | title()}} </h1>
↳ Um Título Para A Página
# substituir um trecho em uma string
{{ "Bom dia galera!" | replace("Bom dia", "Boa noite") }}
↳ Boa noite galera!
# inverter a ordem dos elementos
{{ "Olá galera!" | reverse() }}
↳ !arelag álO
Conversores
Por default os valores passados em uma url e capturados como valores do python são strings. Alguns conversores podem transformar essas strings em caminhos (que usam barras / ), inteiros ou decimais.
@app.route("/usuario/<int:id>")
def exibir_id(id):
# esta função recebe id como um inteiro e o exibe
return f"O id digitado é {id}"
@app.route("/path/")
def exibir_caminho(caminho):
# recebe e retorna o caminho passado
return f"Caminho {caminho}"
Os seguintes conversores estão disponívies:
string
(default) qualquer string sem barras / ou \
int
converte em inteiros positivos
float
converte em números decimais
path
strings contendo barras de caminho
uuid
strings UUID†
† Uma string UUID (Universally Unique IDentifier), também chamada de GUID (Globally Unique IDentifier) é um número de 128-bits usado na troca de informações em computação.
Valores numéricos podem ser convertidos entre inteiros e decimais, e um valor default ser fornecido.
# números inteiros podem ser convertidos em decimais, ou decimais em inteiros
{{ 10 | float() }}
↳ 10.0 ou 0.0 # 0.0 se a conversão não for possível
{{ 10.0 | int() }}
↳ 10
# um valor default, em caso de erro
{{ "qualquer" | float (default = "Erro: texto não pode ser convertido em decimal") }}
↳ Erro: texto não pode ser convertido em decimal
Manipulação de Listas
Diversas operações são disponíveis em listas.
# join: junta elementos de uma lista
{{ [1, 2, 3] | join() }}
↳ 123
{{ ["Um", "Dois", "Tres"] | join() }}
↳ UmDoisTres
# inserindo um separador
{{ [1, 2, 3] | join ("|") }}
↳ 1|2|3
{{ ["Um", "Dois", "Tres"] | join("-") }}
↳ Um-Dois-Tres
# o filtro list() retorna uma lista
{{ "Guilherme" | list()}}
↳ ["G","u","i","l","h","e","r","m","e"]
# random() seleciona um item aleatorio da lista
{{ ["Mercúrio", "Venus", "Terra"] | random() }}
↳ Venus
{% set pe_pagina = ["citacao 1", "citacao 2", "citacao 3", "citacao 4", "citacao 5"] %}
{{ pe_pagina | random() }}
↳ citacao 4
# replace (visto acima para strings) também pode ser usado em listas
{% set lista = ["Nada", "a", "dizer"] %}
{{ lista | replace ("Nada", "Tudo") }}
↳ ["Tudo", "a", "dizer"]
# o filtro reverse() também pode inverter uma lista
# mas seu resultado é um objeto iterador
{% set lista = ["unidade", "dezena", "centena"] %}
{{ list | reverse() }}
↳ <list_reverseiterator object at 0x7fc0b6262518>
# para usar o objeto lista sem usar iterações temos que usar o método list()
{{ list | reverse() | list() }}
↳ ["centena", "dezena", "unidade"]
O filtro random() pode ser útil para exibir um artigo aleatório do site na homepage, para escolher uma imagem ou um pé de página, etc.
Outros exemplos de manipulação de listas incluem o uso de first(), last(), uso de índices e de laços para percorrer toda a lista.
# first() é 1º elemento da lista, last() é o último elemento
{% set nomes = ["João", "Pedro", "da", "Silva"] %}
<p> Nome: {{ nomes | first() }} </p>
<p> Segundo Nome: {{ nomes [1] }} </p>
<p> Sobrenome: {{ nomes | last() }} </p>
↳ Nome: João
↳ Segundo Nome: Pedro
↳ Sobrenome: Silva
# o tamanho de uma lista é retornado com {{ lista | length }}
# laços for são usados para percorrer os elementos
{% set comentarios = ["Comenta 1", "Comenta 2", "Comenta 3", "Comenta 4"]%}
<p>Temos ({{comentarios | length}}): comentários</p>
{% for comentario in comentarios %}
<p> {{ comentario }} </p>
{% endfor%}
# resulta em
↳ Temos 4: comentários
↳ Comenta 1
↳ Comenta 2
↳ Comenta 3
↳ Comenta 4
O filtro safe
O filtro safe serve para passar para o interpretador do Flask a informação de que as tags html devem ser renderizadas. Sem ele a string "<texto>" é exibida literalmente, inclusive com os delimitadores "<>".
Por motivo de segurança o Jinja2 remove as tags html. Por exemplo: uma variável com valor "<li> TEXTO </li>" será renderizada como "<li> TEXTO </li>" por extenso e sem provocar a renderização do navegador. Com o filtro safe o TEXTO é exibido como um ítem de lista.
# exibição literal de uma string
{{ "<b>Texto a exibir!</b>" }}
↳ <b>Texto a exibir!</b>
# para forçar a renderização da tag <b> (negrito)
{{ "<b>Texto a exibir!</b>" | safe }}
↳ Texto a exibir!
# define uma lista
{% set lista = ["<li>Um elefante</li>", "<li>Dois elefantes</li>", "<li>Três elefantes</li>"] %}
<ul>
{% for item in list %}
{{ item | safe }}
{% endfor %}
</ul>
# será renderizado como
↳
⏺ Um elefante
⏺ Dois elefantes
⏺ Três elefantes
# alternativamente
{% set lista = ["Um elefante", "Dois elefantes", "Três elefantes"] %}
<ul>
{% for item in list %}
<li> {{ item }} </li>
{% endfor %}
</ul>
# será renderizado da mesma forma.
# Nesse caso não existem tags na lista e safe é desnecessário.
Observação importante: Qualquer input digitado por usuários deve passar pelo filtro safe para evitar que alguma instrução danosa seja processada pelo navegador.
Laços e bifurcações
Vimos que um template recebe variáveis do python e pode processá-las com código. Por exemplo, modificamos o template frutas.html da seguinte forma:
# frutas.html
<body>
{% if nome_da_fruta == None %}
<p>Você não escolheu uma fruta!</p>
{% elif nome_da_fruta == "laranja" %}
<p>Você escolheu laranja, a melhor fruta!</p>
{% else %}
<p>Você escolheu a fruta: {{ nome_da_fruta }}</p>
{% endif %}
</body>
No código de meu_site.py modificamos a função frutas para que por default ela receba None (caso nada seja escrito após o nome do diretório /frutas/:
No template, os trechos entre chaves não são parte do html e sim do Python, gerenciado pelo Flask. Dessa forma podemos integrar as páginas da web com as inúmeras bibliotecas do Python.
Quatro tipos de marcações estão disponíveis para para inserção do código nos templates.
sintaxe
usadas para
{% ... %}
linhas de instruções
{{ ... }}
expressões
{# ... #}
comentários
# ... ##
instruções inline
Variáveis
Variáveis a serem usadas nos templates podem ser de qualquer tipo. Por exemplo:
{% set nomes = ["João", "Pedro", "da", "Silva"] %}
<p>O segundo nome da lista: {{ nomes[1] }}.</p>
↳ O segundo nome da lista: Pedro.
{% set id = 3 %}
<p>Quem? {{ nomes[id] }}!</p>
↳ Quem: Silva!
# uso de um dicionário
{% set dicionario = {"Nome":"Paul"; "Sobrenome":"Dirac"; "Profissão":"Físico"} %}
<p>Estes são os dados do: {{ dicionario["Nome"] }}.</p>
{% for chave, valor in dicionario.items() %}
<p>{{ chave }} : {{ valor }}</p>
{% end for %}
↳ Estes são os dados do: Paul.
↳ Nome: Paul
↳ Sobrenome: Dirac
↳ Profissão :Físico
# no caso geral, se um objeto é passado para o template e tem um método, podemos usar:
<p>Obtendo um valor com um método de objeto disponível: {{ objeto.metodo() }}.</p>
Incluindo trechos com include
Se uma parte do template é repetida várias vezes ela pode ser colocada à parte, em arquivo separado, e incluída na template principal. Por ex., se temos um pé de página que aparece em diversas de nossas páginas ele pode ser gravado à parte.
# arquivo pe_de_pagina.html
<div class="pe_pagina">
<p>Esté é o meu pé de página</p>
</div>
Esse código assume que existe a definição de uma classe css chamada pe_pagina.
Em todos os arquivos que devem exibir o pé de página inserimos:
# todo o texto da página
# pé de página
{% include 'pe_de_pagina.html' %}
Macros
Macros são formas de implementar o princípio DRY (Don’t Repeat Yourself ), uma prática de desenvolvimento de software que visa reduzir a repetição do linhas semelhantes ou com mesma função no código. Ele torna o código mais legível e de fácil manutenção, uma vez que alterações devem ser feitas em um único bloco. Templates, macros, inclusão de conteúdo externo (como em include()) e heranças de modelos são todos instrumentos utilizados para isso.
Outra forma de gerar código reutilizável é através da criação de macros, um recurso similar às funções usuais do Python. Macros podem ser gravadas em arquivos separados e importadas dentro de todos os templates que fazem uso delas, facilitando a modularização do código.
Uma macro pode executar tarefas simples como simplesmente montar as linhas da lista. Suponha que temos uma lista de linhas e queremos montar uma lista não ordenada em html:
{% macro montar_lista(linha) %}
<li>{{ linha }}</li>
{% endmacro %}
<ul>
{% for linha in linhas %}
{{ montar_lista(linha) }}
{% endfor %}
</ul>
Outra possibilidade importante para a modularização consiste em gravar um arquivo macros.html que contém a macro vermelho(texto, marcas). Ele retorna linhas de uma lista coloridas de vermelho se texto == marcas, de azul caso contrário.
O arquivo mostrar_frutas.html importa a arquivo anterior, com a sua macro, e faz uso dela para exibir a lista ordenada.
# arquivo mostrar_frutas.html
{% from "macros.html" import vermelho %}
{% set frutas = ["Abacate", "Abacaxi", "Laranja", "Uva"] %}
{% set selecionado = "Abacaxi" %}
<ol>
{% for fruta in frutas %}
{{ vermelho(fruta, selecionado) }}
{% endfor %}
</ol>
O resultado no navegador é o seguinte:
Abacate
Abacaxi
Laranja
Uva
Assim como é válido no Python, podemos fazer a importação de forma alternativa (mostrando só linhas diferentes):
# arquivo mostrar_frutas.html
{% import "macros.html" as macros %}
{% for fruta in frutas %}
{{ macros.vermelho(fruta, selecionado) }}
{% endfor %}
Herança de Templates
Similar à herança de classes no modelo POO do python, podemos criar um template base e derivar dele outros templates que herdam a sua estrutura. Os templates base definem blocos que podem ser sobrescritos nos templates filhos. Um template base pode ter a seguinte estrutura:
# arquivo base.html
<html>
<head>
{% block head %}
<title> Artigo {% block title %}{% endblock %} </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
{% block final %}
<p>Site construído com Python-Flask!</p>
{% endblock %}
</body>
</html>
A instrução {% block nome_do_bloco %}{% endblock %} pode ser substituída por conteúdo nos templates filhos (ou derivados). Para herdar desse template usamos extends e redefinimos os blocos da base:
# arquivo derivado.html
{% extends "base.html" %}
{% block title %}Nome do Artigo{% endblock %}
{% block head %}
<style>
# estilos css ficam aqui
</style>
{% endblock %}
{% block body %}
<h1>Nome do Artigo</h1>
<p>Texto do artigo</p>
{% endblock %}
{% block final %}
{{ super() }}
{% endblock %}
O bloco final, {% block final %}{{ super() }}{% endblock %}, usa super() para simplesmente importar o conteúdo do arquivo base. Se a base e o derivado contém texto o conteúdo da base é sobreposto.
Solicitações e Respostas do Servidor (Request, Response)
Uma thread (ou um fio) é um bloco de execução criado por um processo (um aplicativo em execução). Se várias threads são executadas ao mesmo tempo, temos uma multithreading. Multithreading é um modelo de execução de programas que permite que várias threads sejam criadas dentro do mesmo processo, executadas independentemente mas com recursos compartilhados. Em CPUS de vários núcleos as threads podem ser executadas em paralelo. Caso contrário são distribuídos em núcleo único, alternando a threads ativa.
Ao receber uma solicitação de um cliente o Flask responde passando para as funções de visualização (view functions) os objetos que serão usados para a construção da página web. Um exemplo é o objeto request, que contém a solicitação HTTP enviada pelo cliente. Temos que nos lembrar que o aplicativo pode receber um grande volume de solicitações em múltiplas threads†. Para evitar que todas as funções de visualização recebam essas informações como parâmetros, o que pode tornar o código complexo e gerar conflitos o Flask usa contextos para que esses objetos fiquem temporariamente acessíveis dentro desses contextos.
Para os exemplos que se seguem vamos trabalhar usando o python no terminal. Para isso ativamos o ambiente virtual e o próprio python:
$ cd ~/caminho_para_o_projeto
$ source ./bin/activate
# o prompt muda para indicar ativação de venv
$ (venv) $ python
Python 3.9.10 (main, Jan 17 2022, 00:00:00)
Graças aos contextos, funções de visualização como a seguinte podem ser escritas:
from flask import request
@app.route('/')
def index():
navegador = request.headers.get('User-Agent')
return f'<p>Verificamos que seu navegador é o {navegador} </p>'
Que retorna no navegador (no meu caso):
Verificamos que seu navegador é o Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0
Contextos: Note que request não foi passado explicitamente para index(), agindo como se fosse uma variável global. Isso é obtido pelo Flask com os contextos ou ambientes reservados. Dois ambientes são usados: o contexto de aplicativo e o contexto de requisição.
As seguintes variáveis existem nesses contextos:
nome da variável
Contexto
Descrição
current_app
Aplicativo
A instância do aplicativo ativo.
g
Aplicativo
Objeto usado pelo aplicativo para armazenamento de dados durante uma requisição. Resetada a cada requisição.
request
Requisição
Objeto que encapsula o conteúdo da requisição HTTP enviada pelo cliente.
session
Requisição
Representa a sessão do usuário, um dicionário que o aplicativo usa para armazenar valores entre requisições.
Esses contextos só estão disponíveis quando o aplicativo recebe uma requisição (por meio de uma url digitada no navegador). O Flask ativa o contexto de aplicativo disponibilizando current_app e g, e o contexto de requisição disponibilizando request e session, para a thread, e em seguida os remove. Um exemplo desse comportamento pode ser visto no código, executado dentro do python:
from meu_site import app
from flask import current_app
current_app.name # um mensagem de erro é exibida
# pois o contexto não está ativo
RuntimeError: working outside of application context
app_contexto = app.app_context()
app_contexto.push() # o flask ativa o contexto
current_app.name # o nome do app é impresso
'meu_site'
app_contexto.pop()
Flask usa os métodos objeto.push() e objeto.pop() para iniciar e terminar um contexto. A variável current_app.name só existe enquanto o contexto está ativado.
Preparando uma resposta
Para responder a uma solicitação o Flask armazena um mapa que associa URLs (e suas partes) à função resposta que deve ser executada. Esse mapa está armazendo em app.url_map.
A lista mostra o mapeamento das funções view que criamos e mais um, denominado /static, acrescentado automaticamente para acessar arquivos estáticos como arquivos de estilo (cascading style sheets, *.css) e imagens. Os elementos (HEAD, OPTIONS, GET) são passados dentro da URL. Os dois primeiros são gerenciados internamento pelo Flask.
Objeto request
O objeto request, armazenado na variável request, contém toda a informação passada na requisição pela URL. Os atributos e métodos mais comuns desse objeto são listados a seguir.
Atributo/Método
Descrição
form
Dicionário com todos os campos de form submetidos na requisição.
args
Dicionário com todos os argumentos passados na string de pesquisa da URL.
values
Dicionário com valores combinados de form e args.
cookies
Dicionário com todos os cookies incluídos na requisição.
headers
Dicionário com todos os cabeçalhos HTTP incluídos na requisição.
files
Dicionário com todos os arquivos de upload incluídos na requisição.
get_data()
Retorna dados em buffer na requisição.
get_json()
Retorna dicionário com dados JSON incluído na requisição.
blueprint
Nome do blueprint† que está processando a requisição.
endpoint
Nome do endpoint processando a requisição. Flask usa a função view como nome do endpoint para um caminho.
method
Método da requisição HTTP, (GET ou POST).
scheme
Esquema da URL (http or https).
is_secure()
Retorna True se a requisição veio de conexão segura (HTTPS).
host
O host definido na requisição, incluindo o número da porta se fornecido pelo cliente.
path
Parte da URL que define o caminho.
query_string
Parte da URL que define a string de pesquisa (query), como um valor binary (raw).
full_path
Parte da URL que define caminho e pesquisa (query).
url
Requisição completa da URL fornecida pelo cliente.
base_url
O mesmo que url, sem a parte de pesquisa (query).
remote_addr
Endereço de IP do cliente.
environ
Dicionário com o ambiente de WSGI da requisição.
(†) Blueprints serão vistos mais tarde.
Hooks de Solicitação (request hooks)
Com o Flask podemos registrar funções que devem ser chamadas antes ou depois de uma solicitação. Essas funções podem ser usadas para executar tarefas úteis, tais como autenticar um usuário, abrir e fechar a conexão com um banco de dados, etc. Quatro ganchos (hooks) são disponibilizados:
before_first_request
Registra função para execução antes da primeira requisição. Útil para tarefas de inicialização do servidor.
before_request
Registra função a ser executada antes de cada requisição.
after_request
Registra função a ser executada após cada requisição, caso não ocorram exceções não tratadas.
teardown_request
Registra função a ser executada após cada requisição, mesmo que ocorram exceções não tratadas.
Um exemplo de uso desses hooks seria o de usar before_request para coletar dados que serão usados ao longo do ciclo de vida do aplicativo para um usuário e os armazenar na variável g para uso posterior.
Como vimos, uma requisição resulta em uma resposta por meio de uma das funções view enviada ao cliente. Ela pode ser uma página simples de html construída com auxílio dos templates ou algo mais complexo. Junto com a resposta, de acordo com o protocolo HTTP, é enviado um código de status (status code) indicando o sucesso da solicitação. Por default o Flask retorna o status_code = 200 para solicitação bem sucedida. Podemos usar uma função view para retornar outro código.
@app.route('/')
def index():
return 'Ocorreu um erro!', 400
A função acima retorna uma tupla com uma string e um inteiro. É possível e útil fazer com que essas funções retornem um objeto response no lugar da tupla.
from flask import make_response
@app.route('/')
def index():
response = make_response('Resposta Final sobre o Universo!')
response.set_cookie('resposta', '42') return response
Dessa forma response passa a conter um cookie (que é gerenciado pelo navegador que o recebe). A tabela seguinte mostra métodos e atributos mais usados no objeto response.
Métodos e atributos do objeto response
Atributo/Método
Descrição
status_code
Código numérico de status do HTTP
headers
Objeto tipo dicionário com todos os cabeçalhos a serem eviados em response
set_cookie()
Acrescenta um cookie no objeto response
delete_cookie()
Remove um cookie
content_length
Comprimento do corpo da response
content_type
Tipo de midia do corpo da response
set_data()
Define o corpo da response como string ou bytes
get_data()
Retorna o corpo da response
Dois tipos de resposta especiais para casos que ocorrem com frequência são fornecidos como funções auxiliares. Uma delas é uma forma de lidar com erros, eviando um código por meio do método abort(). No exemplo abaixo usamos essa função para enviar uma mensagem 404 (página não encontrada) caso a id de um usuário não seja encontrada com load_user(id).
from flask import abort
@app.route('/usuario/<id>')
def usuario(id):
usuario = load_user(id)
if not usuario:
abort(404)
return f'<div class="usuario">Bem vindo {usuario.name}</div>'
Esse exemplo supõe que exista um template para usuario e que ele carrega instruções css para a classe usuario.
Se load_user(id) retornar None é executada abort(404) que retorna a mensagem de erro. Uma exceção é levantada e a função usuario() é abandonada antes de atingir a instrução return.
O outro tipo de resposta é o redirect que não retorna uma página mas sim uma nova URL redirecionando o navegador. redirect retorna status_code = 302 e a nova URL (dada no código).
from flask import redirect
@app.route('/')
def index():
return redirect('https://phylos.com/programacao')
Para a construção de páginas e aplicativos web é essencial algum conhecimento de html e css, não cobertos nesse artigo.
Um aplicativo web web application ou simplesmente web app) é um aplicativo executado através de um servidor web, diferente de um aplicativo executado na máquina local, e geralmente rodados e visualizados por meio de um browser ou navegador. Eles podem ser um conjunto de páginas de texto dinâmicas contendo, por exemplo, pesquisas em uma biblioteca, um gerenciador de arquivos na nuvem, um gerenciador de contas bancárias ou emails, um servidor de músicas ou filmes, etc. Com frequência esses aplicativos estão conectados a um banco de dados, podendo fazer neles consultas e modificações.
Um framework para a web é um conjunto de softwares destinados a oferecer suporte ao desenvolvimento de aplicativos na Web. Eles buscam automatizar a construção dos web apps e seu gerenciamento durante o funcionamento, após sua publicação na web. Alguns frameworks web incluem bibliotecas para acesso a banco de dados, templates para a construção dinâmica das páginas e auxílio à reutilização de código. Eles podem facilitar o desenvolvimento de sites dinâmicos ou, em alguns casos, de sites estáticos.
Framework Flask
O Flask é um micro-framework em Python usado para desenvolvimento e gerenciamento de web apps. Ele é considerado micro porque possui poucas dependências para seu funcionamento e pode ser usado com uma estrutura inicial bem básica, voltada para aplicações simples. Apenas duas bibliotecas são instaladas junto com o Flask. Ele não contém, por ex., um gerenciador de banco de dados ou um servidor de email. No entanto esses serviços podem ser acrescentados para ampliar as funcionalidades do aplicativo.
Em particular, o Flask não inclui uma camada de abstração com banco de dados. Em vez disso é possível instalar extensões, escolhendo o banco de dados específico que se quer usar. Essas extensões também podem auxiliar a validação de formulário, manipulação de upload, tecnologias de autenticação aberta, entre outras.
Flask foi desenvolvido por Armin Ronacher e lançado em 2010. Algumas vantagens citadas em seu uso são: (a) projetos escrito com a Flask são simples (comparados àqueles gerados por frameworks maiores, como o Django) e tendem a ser mais rápidos. (b) Ele é ágil e modular: o desenvolvedor se concentra apenas nos aspectos utilizados de seu aplicativo, podendo ampliar sob demanda. (c) Os projetos são pequenos mas robustos. (d) Existe uma vasta comunidade de desenvolvedores contribuindo com seu desenvolvimento, apresentando bibliotecas que ampliam sua funcionalidade.
Criando um aplicativo básico
Nessa primeira parte vamos criar um aplicativo bem básico mas funcional. Depois entraremos em outros detalhes do Flask.
Para criar um projeto usando o Flask (ou, na verdade, outro projeto qualquer) é aconselhável criar antes um ambiente virtual para que os pacotes instalados não conflituem com outros em projetos diferentes. A criação e manutenção de ambientes virtuais está descrita na página Ambientes Virtuais, PIP e Conda. Alguns IDEs, como o Pycharm realizam automaticamente esse processo. No meu caso ele ficará abrigado em ~/Projetos/flask/venv. Para simplificar denotarei esse ambiente simplesmente pelo nome flask, que é também o nome da pasta que abriga esse projeto.
Nesse ambiente instalamos o Flask com pip install flask. Uma estrutura básica de ambiente já estará montada após esse passo. Em seguida criamos um arquivo do python, de nome meu_site.py.
# meu_site.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def homepage():
return "Esta é a minha homepage"
if __name__ == "__main__":
app.run()
# é exibido no console do python
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Esse arquivo não deve se chamar flask.py para evitar conflito de nomes.
A variável __name__, passada para o construtor do Flask contém o nome do módulo principal e é usada para determinar a localização do aplicativo no app. Outros diretórios e arquivos, como pastas de templates, arquivo de estilo e imagens, serão localizadas à partir dessa raiz.
Aplicativos do Flask incluem um servidor de desenvolvimento que pode ser iniciado com o comando run. Esse comando busca pelo nome do aplicativo na variável de ambiente FLASK_APP. Se queremos rodar o aplicativo meu_site.py executamos na linha de comando:
# Linux ou macOS
(venv) $ export FLASK_APP=meu_site.py
(venv) $ flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
# Microsoft Windows
(venv) $ set FLASK_APP=meu_site.py
(venv) $ flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
# alternativamente se pode iniciar o servidor com
app.run()
No Pycharm, ou outros IDES, você pode executar diretamente esse código.
Da biblioteca flask importamos apenas (por enquanto) a classe Flask. Uma instância da classe é criada com app = Flask(__name__) onde a variável __name__ contém o nome do projeto. A linha @app.route("/") é um decorador que informa que a função seguinte será rodada na raiz / do site. Quando esse arquivo .py é executado dentro de uma IDE ou usando python meu_site.py, na linha de comando, é exibido no console várias mensagens, entre elas a urlhttp://127.0.0.1:5000/, que pode ser clicada ou copiada para a linha de endereço do navegador. Isso resulta na exibição, dentro do navegador, da página:
Esta é a minha homepage
Clientes e Servidores: O navegador age como o cliente que envia ao servidor uma solicitação, através de uma URL digitada na barra de endereços. O servidor da web transforma essa solicitação em ações a serem realizadas do lado do servidor e retorna uma página com conteúdo de texto e multimídia, renderizados pelo navegador. O Flask fica do lado do servidor, construindo a resposta. Entre outras coisas ele possui um mapeamento entre as URLs e as funções route() que serão executadas no código *.py.
O endereço e a porta 127.0.0.1:5000 são padrões para o Flask. app.run() cria um servidor que atende à requisição HTTP do navegador, exibindo a página html. Qualquer texto retornado pela função homepage() é renderizado no formato html e exibido no navegador. Por exemplo, se fizermos as alterações, colocando o texto entre tags h1:
@app.route("/")
def homepage():
return "<h1>Esta é a minha homepage</h1>"
if __name__ == "__main__":
app.run(debug=True)
o texto agora é renderizado como um título de nível 1:
Esta é a minha homepage
o mesmo texto será exibido mas agora com formatação de título, a tag h1. Todas as demais tags podem ser utilizadas. O parâmetro debug=True faz com que alterações no código sejam imediatamente repassadas para as requisições ao servidor, sem a necessidade de rodar todo o projeto novamente. Com isso basta recarregar a página do navegador para que alterações sejam exibidas, clicando no ícone de atualização ou pressionando F5. No mode debug os módulos dois módulos chamados reloader e debugger estão ativados por default. Com o debugger ativado as mensagens de erro são direcionadas para a página exibida. O mode debug nunca deve ser ativado em um servidor em produção pois isso fragiliza a segurança do site.
Também podemos ativar o módulo no código que executa o aplicativo:
O decorador @app.route("/") registra a função homepage() junto com a página raiz do site. Outras páginas vão executar outras funções. Por exemplo, uma página de contatos pode ser inserida por meio da inserção de nova função no código. Nesse caso criaremos a função contatos().
# meu_site.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def homepage():
return "<h1>Esta é a minha homepage</h1>"
@app.route("/contatos")
def contatos():
txt = (
"<h1>Página de Contatos</h1>"
"<ul>"
"<li>Contato 1</li>"
"<li>Contato 2</li>"
"</ul>"
)
return txt
if __name__ == "__main__":
app.run(debug=True)
Usamos acima a concatenação de string com parênteses: (str1 str2 ... strn).
Agora, além da homepage temos a página de contatos em 127.0.0.1:5000/contatos, com a seguinte aparência.
Página de contatos
Contato 1
Contato 2
A funções contatos() e homegage() são chamadas funções de visualização (view functions).
Html e templates: Notamos agora que o código em meu_site.py contém sintaxe misturada de Python e html e pode ficar bem complexo em uma página real exibida na web. Para evitar isso o Flask permite a criação de templates. Fazemos isso da seguinte forma: no diretório raiz onde está o projeto (o mesmo onde foi gravado meu_site.py) criamos o diretório template (o nome default do Flask). Dentro dele colocamos nossos templates. Por exemplo, criamos os arquivos homepage.html,
# homepage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Homepage: teste Flask</title>
</head>
<body>
<h1>Este é o título da Homepage</h1>
<p>Com os devidos parágrafos...</p>
</body>
</html>
Quando esse código é executado temos a referência ao link que, se aberto, mostra as páginas criadas: digitando 127.0.0.1:5000 abrimos nossa homepage:
Este é o título da Homepage
Com os devidos parágrafos…
Mas, se digitarmos 127.0.0.1:5000/contatos a outra página é exibida:
Página de Contatos
Contato 1
Contato 2
Uma página pode receber parâmetros do código em python. Por exemplo, digamos que queremos exibir uma página para cada produto existente em uma loja virtual que vende frutas. Nesse caso acrescentamos no código de meu_site.py:
Se for digitado no campo de endereços do navegador, ou passado por meio de um link na tag <a href="http://127.0.0.1:5000/frutas/laranja">Laranja</a> a parte do endereço <nome_da_fruta> = laranja é passado como valor de parâmetro na função frutas("laranja") que é disponibilizado dentro do código html como {{nome_da_fruta}}.
Resumindo: @app.route("/frutas/<nome_da_fruta>") envia uma string na variável nome_da_fruta para a função frutas que, por sua vez repassa ao código html. Dentro do html a variável fica disponível como {{nome_da_fruta}} (dentro de uma dupla chave).
Por exemplo, se digitamos na barra de endereços do navegador http://127.0.0.1:5000/frutas/laranja teremos a exibição de
Frutas
Você escolheu a fruta: laranja
Essa técnica pode ser usada, por ex., para criar páginas para diversos usuários usando um único template usuario.html.
@app.route('/usuario/<nome>')
def usuario(nome):
return render_template("usuario.html")
# ou
@app.route('/usuario/<int:id>')
return render_template("usuario.html")
A parte do código <int:id> é um filtro que transforma a entrada digitada em inteiro, quando possível e será melhor explicada adiante.
Formatando com CSS
O texto dentro de uma página html (HyperText Markup Language) pode ser formatado de algumas formas diferentes, usando css (Cascading Style Sheets). Quando se trata do uso de um framework a forma preferida consiste em apontar no cabeçalho para um arquivo externo css. No Flask isso é feito da seguinte forma: um arquivo css é gravado na pasta static da pasta do projeto. Digamos que gravamos o arquivo /static/styles.css com um conteúdo mínimo, apenas para demonstração, tornando vermelhas as letras do título e azuis as letras dos parágrafos:
Com todas essas alterações o projeto tem agora a estrutura de pastas mostrada. Na figura à esquerda todas as pastas, inclusive aquelas criadas pelo gerenciador de ambientes virtuais são mostradas. No meu caso elas foram criadas automaticamente pelo IDE Pycharm, mas podem ser criadas pelo programador sem dificuldade. Na figura à direita são mostradas apenas as pastas criadas pela programador diretamente. Um projeto com esse formato roda perfeitamente, apesar de não contar com as vantagens do ambiente virtual (veja artigo).
Outras estruturas de código podem ser inseridas nos templates, como veremos.
Opções de comando de linha
Quando se roda o flask diretamente no terminal podemos ver uma mensagem de ajuda com (venv) $ flask --help, verificar os caminhos definidos no app ou entrar em uma shell interativa.
# Exibir ajuda do flask
(venv) $ flask --help
Usage: flask [OPTIONS] COMMAND [ARGS]...
A general utility script for Flask applications.
Provides commands from Flask, extensions, and the application. Loads the
application defined in the FLASK_APP environment variable, or from a wsgi.py
file. Setting the FLASK_ENV environment variable to 'development' will
enable debug mode.
$ export FLASK_APP=hello.py
$ export FLASK_ENV=development
$ flask run
Options:
--version Show the flask version
--help Show this message and exit.
Commands:
routes Show the routes for the app.
run Run a development server.
shell Run a shell in the app context.
# exibe os caminhos ativos no aplicativo
(venv) $ flask routes
Endpoint Methods Rule
-------- ------- -----------------------
contatos GET /contatos/
frutas GET /frutas/
frutas GET /frutas/
homepage GET /
static GET /static/
# entra em uma shell interativa
(venv) $ flask shell
A shell do flask inicia uma sessão python no contexto do atual aplicativo onde podemos executar testes ou tarefas de manutenção. O comando flask run admite vários parâmetros:
(venv) $ flask run --help
Usage: flask run [OPTIONS]
Run a local development server.
This server is for development purposes only. It does not provide the
stability, security, or performance of production WSGI servers.
The reloader and debugger are enabled by default if FLASK_ENV=development or
FLASK_DEBUG=1.
Options:
-h, --host TEXT The interface to bind to.
-p, --port INTEGER The port to bind to.
--cert PATH Specify a certificate file to use HTTPS.
--key FILE The key file to use when specifying a
certificate.
--reload / --no-reload Enable or disable the reloader. By default
the reloader is active if debug is enabled.
--debugger / --no-debugger Enable or disable the debugger. By default
the debugger is active if debug is enabled.
--eager-loading / --lazy-loading
Enable or disable eager loading. By default
eager loading is enabled if the reloader is
disabled.
--with-threads / --without-threads
Enable or disable multithreading.
--extra-files PATH Extra files that trigger a reload on change.
Multiple paths are separated by ':'.
--help Show this message and exit.
O argumento --host informa ao servido qual é o ambiente web que pode acessar nosso servidor de desenvolvimento. Por default o servidor de desenvovimento do Flask só aceita chamadas do computador local, em localhost. Mas é possível configurá-lo para receber chamadas da rede local ou de ambientes mais amplos. Por exemplo, como o código
(venv) $ flask run --host 0.0.0.0
* Serving Flask app "hello"
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
todos os computadores conectados pelo mesmo ip terão acesso ao aplicativo.
Implantação de um aplicativo Flask
O processo de implantação (ou deploy) de um aplicativo consiste nas etapas necessários para colocá-lo acessível para seus usuários. No caso de um aplicativo web a implantação significa estabelecer um servidor ou usar servidores já disponíveis, que os usuários possam acessar, e colocar seu aplicativo como um de seus serviços.
O desenvolvimento do aplicativo se dá em um ambiente de desenvolvimento onde podem existir condições próprias para o debug e nem todas as medidas de segurança estão implementadas. Depois ele passa para a etapa de uso, no ambiente de produção. Uma conta no Heroku pode ser criada, e um site com poucos acessos pode ser mantido sem custos. Se o site for escalonado e crescer a conta deve ser atualizada e paga. A lista abaixo contém links para o Heroku e outros provedores.