SQL, ou Structured Query Language, é uma linguagem de programação que nos permite atualizar e consultar bancos de dados.

Antes de entrarmos em como usar a linguagem SQL, devemos discutir como nossos dados são armazenados. Ao usar SQL, trabalharemos com um banco de dados relacional onde todos os nossos dados são armazenados em várias tabelas. Cada uma dessas tabelas é composta por um número fixo de colunas e um número variável de linhas.
Para ilustrar como trabalhar com SQL, usaremos o exemplo de um site de cursos, que armazena dados do curso, usuários, entre outras coisas. Um exemplo de tabela que iremos ver está a seguir:

Existem vários sistemas de gerenciamento de banco de dados relacionais diferentes que são comumente usados para armazenar informações e que podem interagir facilmente com comandos SQL:
Os dois primeiros, MySQL e PostgreSQL, são sistemas de gerenciamento de banco de dados mais robustos que normalmente são executados em servidores separados daqueles que executam um site. SQLite, por outro lado, é um sistema mais leve que pode armazenar todos os seus dados em um único arquivo. Usaremos SQLite ao longo deste curso, pois é o sistema padrão usado pelo Django.
Assim como trabalhamos com vários tipos de variáveis em Python, SQLite tem tipos que representam diferentes formas de informação. Outros sistemas de gerenciamento podem ter tipos de dados diferentes, mas todos são bastante semelhantes aos do SQLite:
TEXT: Para strings de texto (Ex. nome de uma pessoa)NUMERIC: Uma forma mais geral de dados numéricos (Ex. Uma data ou valor booleano)INTEGER: Qualquer número não decimal (Ex. idade de uma pessoa)REAL: Qualquer número real (Ex. peso de uma pessoa)BLOB (Binary Large Object): Qualquer outro dado binário que possamos querer armazenar em nosso banco de dados (Ex. uma imagem)Agora, para realmente começar a usar SQL para interagir com um banco de dados, vamos começar criando uma nova tabela. O comando para criar uma nova tabela é mais ou menos assim:
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
pass_hash TEXT NOT NULL
);
No comando acima, estamos criando uma nova tabela que decidimos chamar de users, e adicionamos quatro colunas a esta tabela:
id: Muitas vezes é útil ter um número que nos permita identificar exclusivamente cada linha em uma tabela. Aqui especificamos que id é um inteiro e também que é nossa chave primária, significando que é nosso identificador único. Além disso, especificamos que ele AUTOINCREMENT, o que significa que não precisaremos fornecer um id toda vez que adicionarmos à tabela, pois isso será feito automaticamente.username: Aqui especificamos que este será um campo de texto, e ao escrever NOT NULL exigimos que ele tenha um valor. Ademais, ao escrever UNIQUE, exigimos que este valor seja único naquela coluna.email: Novamente especificamos que este será um campo de texto e impedimos que seja nulo.password_hash: Por último, especificamos que temos uma senha armazenada como texto, e que não seja nula. Em databases, não devemos guardar uma senha em texto puro! Como exemplo aqui, usamos uma senha com hash. Mas há diferentes maneiras de tratar senhas.Acabamos de ver as restrições NOT NULL, PRIMARY KEY e UNIQUE ao criar uma coluna, mas existem várias outras restrições disponíveis para nós:
CHECK: Garante que certas restrições sejam atendidas antes de permitir que uma linha seja adicionada/modificadaDEFAULT: Fornece um valor padrão se nenhum valor for fornecidoNOT NULL: Garante que um valor seja fornecidoPRIMARY KEY: Indica que esta é a principal forma de buscar uma linha no banco de dadosUNIQUE: Garante que nenhuma duas linhas tenham o mesmo valor nessa coluna.Agora que vimos como criar uma tabela, vamos ver como podemos adicionar linhas a ela. Em SQL, fazemos isso usando o comando INSERT:
INSERT INTO users
(username, email, password_hash)
VALUES ("fulano_dev", "fulano@codelab.br", "2b$12");
No comando acima, especificamos o nome da tabela em que desejamos inserir, depois fornecemos uma lista dos nomes das colunas sobre as quais forneceremos informações e, em seguida, especificamos os VALUES que gostaríamos de preencher nessa linha da tabela, garantindo que os VALUES venham na mesma ordem que nossa lista correspondente de colunas. Observe que não precisamos fornecer um valor para id porque ele é incrementado automaticamente.
Depois que uma tabela é preenchida com algumas linhas, provavelmente queremos uma maneira de acessar os dados dentro dessa tabela. Fazemos isso usando a consulta SELECT do SQL. A consulta SELECT mais simples em nossa tabela de usuários pode ser mais ou menos assim:
SELECT * FROM users;
O comando acima (*) recupera todos os dados da nossa tabela de usuários

Pode ser o caso, porém, que não precisamos realmente de todas as colunas do banco de dados, apenas username e email. Para acessar apenas essas colunas, podemos substituir o * pelos nomes das colunas que gostaríamos de acessar. A consulta a seguir retorna todos os usernames e emails.
SELECT username, email FROM users;

À medida que nossas tabelas ficam cada vez maiores, também queremos reduzir quais linhas nossa consulta retorna. Fazemos isso adicionando um WHERE seguido por alguma condição. Por exemplo, o comando a seguir seleciona apenas a linha com um id de 3:
SELECT * FROM users WHERE id = 3;

Agora que conhecemos alguns comandos básicos de SQL, vamos testá-los no terminal! Para trabalhar com SQLite em seu computador, você deve primeiro baixar o SQLite. (Não usaremos em aula, mas você também pode baixar o DB Browser para uma maneira mais amigável de executar consultas SQL.)
Podemos começar criando um arquivo para nosso banco de dados criando manualmente um novo arquivo ou executando touch users.sql no terminal. Agora, se executarmos sqlite3 users.sql no terminal, seremos levados a um prompt SQLite onde podemos executar comandos SQL:
# Entrando no Prompt SQLite
(base) % sqlite3 users.sql
SQLite version 3.49.1 2025-02-18 13:38:58
Enter ".help" for usage hints.
# Criando uma nova Tabela
sqlite> CREATE TABLE users(
...> id INTEGER PRIMARY KEY AUTOINCREMENT,
...> username TEXT UNIQUE NOT NULL,
...> email TEXT NOT NULL,
...> password_hash TEXT NOT NULL
...> );
# Listando todas as tabelas atuais (Apenas users por enquanto)
sqlite> .tables
users
# Consultando tudo dentro de users (Que está vazio agora)
sqlite> SELECT * FROM users;
# Adicionando um usuário
sqlite> INSERT INTO users
...> (username, email, password_hash)
...> VALUES ("fulano_dev", "fulano@codelab.br", "2b$12");
# Verificando por novas informações, que agora podemos ver
sqlite> SELECT * FROM users;
1|fulano_dev|fulano@codelab.br|2b$12
# Adicionando mais alguns voos
sqlite> INSERT INTO users (username, email, password_hash) VALUES ("ciclano_dev", "ciclano@codelab.br", "a31lW");
sqlite> INSERT INTO users (username, email, password_hash) VALUES ("beltrano_dev", "beltrano@codelab.br", "Wxn96p3");
sqlite> INSERT INTO users (username, email, password_hash) VALUES ("alano_dev", "alano@codelab.br", "3OXePa");
# Consultando essas novas informações
sqlite> SELECT * FROM users;
1|fulano_dev|fulano@codelab.br|2b$12
2|beltrano_dev|beltrano@codelab.br|a31lW
3|ciclano_dev|ciclano@codelab.br|Wxn96p3
4|fulano_dev|fulano@codelab.br|3OXePa
# Alterando as configurações para tornar a saída mais legível
sqlite> .mode columns
sqlite> .headers yes
# Consultando todas as informações novamente
sqlite> SELECT * FROM users;
id username email password_hash
-- ------------ ------------------- -------------
1 fulano_dev fulano@codelab.br 2b$12
2 ciclano_dev ciclano@codelab.br a31lW
3 beltrano_dev beltrano@codelab.br Wxn96p3
4 alano_dev alano@codelab.br 3OXePa
# Procurando apenas por um usuário com email ciclano@codelab.br
sqlite> SELECT * FROM users WHERE email = "ciclano@codelab.br";
id username email password_hash
-- ----------- ------------------ -------------
2 ciclano_dev ciclano@codelab.br a31lW
Também podemos usar mais do que apenas igualdade para filtrar usuários. Para valores inteiros e reais, podemos usar maior que ou menor que:
SELECT * FROM users WHERE id > 1;

E também podemos usar outra lógica (AND, OR) como em Python:
SELECT * FROM users WHERE id > 1 AND email = "alano@codelab.br";

SELECT * FROM users WHERE id > 3 OR email = "fulano@codelab.br";

Também podemos usar a palavra-chave IN para ver se um pedaço de dados é uma de várias opções:
SELECT * FROM users WHERE username IN ("fulano_dev", "ciclano_dev");

Podemos até usar expressões regulares para pesquisar palavras de forma mais ampla usando a palavra-chave LIKE. A consulta abaixo encontra todos os resultados com um f no username, usando % como caractere curinga.
SELECT * FROM users WHERE username LIKE "%f%";

Também há várias funções SQL que podemos aplicar aos resultados de uma consulta. Elas podem ser úteis se não precisarmos de todos os dados retornados por uma consulta, mas apenas de algumas estatísticas resumidas dos dados.
Agora vimos como adicionar e pesquisar tabelas, mas também podemos querer atualizar linhas de uma tabela que já existem. Fazemos isso usando o comando UPDATE como mostrado abaixo. Como você pode ter adivinhado ao ler isso em voz alta, o comando encontra o usuário "alano_dev" e define seu email como alano2@codelab.br .
UPDATE users
SET email = "alano2@codelab.br"
WHERE username = "alano_dev";
Também podemos querer a capacidade de excluir linhas do nosso banco de dados, e podemos fazer isso usando o comando DELETE.
DELETE FROM users WHERE username = "alano_dev";
Existem várias cláusulas adicionais que podemos usar para controlar as consultas que nos retornam
Até agora, trabalhamos apenas com uma tabela por vez, mas muitos bancos de dados na prática são preenchidos por várias tabelas que se relacionam de alguma forma. Como exemplo, no CodeClass, além da tabela de usuários, podemos ter uma tabela de cursos, mostrando cada curso disponível. Então, poderiamos relacionar as duas tabelas.
A tabela de cursos pode ser mais ou menos assim

Dessa forma, podemos relacionar os usuários com cursos. Entretanto, um usuário pode estar inscrito em vários cursos, então se tivéssemos que relacionar os usuários com seus cursos, teríamos que criar várias colunas na tabela users, o que não é intuitivo. Assim, podemos criar mais uma tabela enrollments, que armazena as matrículas dos usuários. Ela se parece como:
Agora temos uma tabela enrollments, onde cada matrícula está relacionada a um usuário e a um curso. Como estamos usando a coluna id da tabela de usuários para preencher user_id e id da tabela de cursos para preencher course_id, chamamos esses valores de Chaves Estrangeiras.
A relação entre users e courses é conhecida como Muitos para muitos.
A tabela de matrículas é conhecida como uma tabela de associação, pois associa as tabelas users e courses.
Dessa forma, criamos diferentes tabelas que irão nos proporcionar uma melhor organização.
Embora nossos dados agora estejam armazenados de forma mais eficiente, parece que pode ser mais difícil consultar nossos dados. Felizmente, SQL tem uma consulta JOIN onde podemos combinar duas tabelas para os propósitos de outra consulta.
Por exemplo, digamos que queremos listar todos os cursos de um usuário, então:
SELECT courses.title
FROM courses
JOIN enrollments ON courses.id = enrollments.course_id
WHERE enrollments.user_id = 1;
Aqui, o que fizemos foi selecionar os títulos dos cursos em que o usuário de id 1 está inscrito.
Para juntar as tabelas de curso e matrículas, procuramos linhas em enrollments onde courses.id = enrollments.course_id, o que conecta os cursos às matrículas.
Acabamos de usar algo chamado INNER JOIN, o que significa que estamos ignorando linhas que não têm correspondências entre as tabelas, mas existem outros tipos de junções, incluindo LEFT JOIN, RIGHT JOIN e FULL OUTER JOIN, que não discutiremos aqui em detalhes.
Uma maneira de tornar nossas consultas mais eficientes ao lidar com tabelas grandes é criar um índice semelhante ao índice que você pode ver no final de um livro didático. Por exemplo, se sabemos que frequentemente procuraremos usuários por username, poderíamos criar um índice de username usando o comando:
CREATE INDEX user_username_index ON users (username);
Agora que sabemos o básico de usar SQL para trabalhar com dados, é importante destacar as principais vulnerabilidades associadas ao uso de SQL. Começaremos com Injeção SQL.
Um ataque de injeção SQL é quando um usuário malicioso insere código SQL como entrada em um site para contornar as medidas de segurança do site. Por exemplo, digamos que nossa tabela de usuários seja mais simples (com username e password), e então um formulário de login na página inicial de um site. Podemos procurar o usuário usando uma consulta como:
SELECT * FROM users
WHERE username = username AND password = password;
Um usuário chamado Harry pode acessar este site e digitar harry como nome de usuário e 12345 como senha, caso em que a consulta seria assim:
SELECT * FROM users
WHERE username = "harry" AND password = "12345";
Um hacker, por outro lado, pode digitar harry" -- como nome de usuário e nada como senha. Acontece que -- representa um comentário em SQL, então a consulta ficaria assim:
SELECT * FROM users
WHERE username = "harry"--" AND password = "12345";
Porque nesta consulta a verificação de senha foi comentada, o hacker pode fazer login na conta de Harry sem saber sua senha. Para resolver este problema, podemos usar:
A outra principal vulnerabilidade quando se trata de SQL é conhecida como Condição de Corrida.
Uma condição de corrida é uma situação que ocorre quando várias consultas a um banco de dados ocorrem simultaneamente. Quando essas não são tratadas adequadamente, podem surgir problemas nos momentos precisos em que os bancos de dados são atualizados. Por exemplo, digamos que eu tenha $150 na minha conta bancária. Uma condição de corrida pode ocorrer se eu fizer login na minha conta bancária tanto no meu telefone quanto no meu laptop e tentar sacar $100 em cada dispositivo. Se os desenvolvedores de software do banco não lidaram com condições de corrida corretamente, então eu posso conseguir sacar $200 de uma conta com apenas $150. Uma solução potencial para este problema seria bloquear o banco de dados. Poderíamos não permitir nenhuma outra interação com o banco de dados até que uma transação seja concluída. No exemplo do banco, depois de navegar para a página "Fazer um Saque" no meu computador, o banco pode não me permitir navegar para essa página no meu telefone.
Django Models são um nível de abstração sobre o SQL que nos permite trabalhar com bancos de dados usando classes e objetos Python em vez de consultas SQL diretas.
Vamos começar a usar models criando um projeto Django para o dev.learn() e criando um app dentro desse projeto.
>> django-admin startproject devlearn
>> cd devlearn
>> python manage.py startapp codeclass
Agora teremos que passar pelo processo de adicionar um app como de costume:
codeclass à lista INSTALLED_APPS em settings.pycodeclass em urls.py:
path("codeclass/", include("codeclass.urls")),
urls.py dentro do aplicativo codeclass. E preencha-o com imports padrão de urls.py e listas.Agora, em vez de criar caminhos reais e começar em views.py, criaremos alguns models no arquivo models.py. Neste arquivo, descreveremos quais dados queremos armazenar em nosso aplicativo. Então, Django determinará a sintaxe SQL necessária para armazenar informações em cada um de nossos models. Nesta etapa, trabalharemos inicialmente com os modelos de cursos, e veremos mais sobre os usuários à frente.
O modelo de cursos pode ser como:
class Course(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(blank=True)
Vamos ver o que está acontecendo nesta definição de model:
Agora, mesmo que tenhamos criado um model, ainda não temos um banco de dados para armazenar essas informações. Para criar um banco de dados a partir de nossos models, navegamos até o diretório principal de nosso projeto e executamos o comando.
>> python manage.py makemigrations
Este comando cria alguns arquivos Python que criarão ou editarão nosso banco de dados para poder armazenar o que temos em nossos models. Você deve obter uma saída que se parece mais ou menos com a abaixo, e se navegar até seu diretório migrations, notará que um novo arquivo foi criado para nós

A seguir, para aplicar essas migrações ao nosso banco de dados, executamos o comando
>> python manage.py migrate
Agora, você verá que algumas migrações padrão foram aplicadas junto com as nossas, e também notará que agora temos um arquivo chamado db.sqlite3 no diretório do nosso projeto

Agora, para começar a adicionar informações e manipular este banco de dados, podemos entrar no shell do Django, onde podemos executar comandos Python dentro de nosso projeto.
python manage.py shell
Python 3.11.2 (main, Nov 30 2024, 21:22:50) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
# Importar nosso model de curso
>>> from codeclass.models import Course
# Criar um novo curso
>>> c = Course(title="Web-Dev", description="Welcome to webdev course!")
# Inserir esse curso em nosso banco de dados
>>> c.save()
# Consultar todos os cursos armazenados no banco de dados
>>> Course.objects.all()
Out: <QuerySet [<Course: Course object (1)>]>
Quando consultamos nosso banco de dados, vemos que obtemos apenas um curso chamado Course object (1). Este não é um nome muito informativo, mas podemos consertar isso. Dentro de models.py, definiremos uma função __str__ que fornece instruções sobre como transformar um objeto Course em uma string:
class Course(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(blank=True)
def __str__(self):
return self.title
Agora, quando voltamos ao shell, nossa saída é um pouco mais legível. Obs: talvez seja necessário sair e entrar do shell. Para recuperar o curso, podemos fazer c = Course.objects.get(title="Web-Dev")
# Criar uma variável chamada courses para armazenar os resultados de uma consulta
>>> courses = Course.objects.all()
# Exibindo todos os cursos
>>> courses
Out: <QuerySet [<Course: Web-Dev>]>
# Isolando apenas o primeiro curso
>>> course = courses.first()
# Imprimindo informações do curso
>>> course
Out: <Course: Web-Dev>
# Exibir id do curso
>>> course.id
Out: 1
# Exibir título do curso
>>> course.title
Out: 'Web-Dev'
# Exibir descrição do curso
>>> course.description
Out: 'Welcome to webdev course!''
Este é um bom começo. Agora, iremos criar um modelo para as matrículas. Devemos lembrar que a tabela de matrículas descreve uma relação Many-to-Many entre usuários e cursos. Para manter simplicidade, iremos também implementar uma tabela de estudantes (que substituirá, por enquanto, a tabela de usuários). Nosso models.py está assim:
from django.db import models
class Course(models.model):
# Definições anteriores aqui
class Student(models.Model):
username = models.CharField(max_length=50, unique=True)
email = models.CharField(unique=True)
bio = models.CharField(blank=True)
def __str__(self):
return self.username
class Enrollment(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='enrollments')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='enrollments')
enrolled_at = models.DateTimeField(auto_now_add=True) # Auto-definido na criação
class Meta:
unique_together = ('student', 'course') # Previne duplicatas
def __str__(self):
return f"{self.student.username} matriculado em {self.course.title}"
As mudanças nos campos student e course dentro da classe Enrollment são novas para nós:
student e course são cada um Foreign Keys, o que significa que se referem a outro objeto.Student como nosso primeiro argumento, estamos especificando o tipo de objeto a que este campo se refere, na primeira linha, assim como ao inserir Course.on_delete=models.CASCADE dá instruções sobre o que deve acontecer se um estudante ou curso for excluído. Neste caso, especificamos que quando algum deles é excluído, todas as matrículas associadas a eles também devem ser excluídas. Existem várias outras opções além de CASCADE.enrolled_at, definimos uma auto-criação da data.Toda vez que fazemos alterações em models.py, temos que fazer migrações e depois migrar. Caso tivéssemos modificado algum campo do modelo Course, deveríamos remover as entradas antigas de cursos.
# Criar Novas Migrações
>> python manage.py makemigrations
# Migrar
>> python manage.py migrate
Agora, vamos testar esses novos models no shell do Django:
# Importar todos os models
>>> from codeclass.models import *
# Criar alguns novos estudantes
>>> ful = Student(username="fulano", email="fulano@codelab.br", bio="Hello")
>>> cic = Student(username="ciclano", email="ciclano@codelab.br", bio="Hi")
# Salvar os estudantes no banco de dados
>>> ful.save()
>>> cic.save()
# Recuperamos um dos cursos com o comando abaixo
>>> c = Course.objects.get(title="Web-Dev")
# Adicionar uma matrícula e salvá-la no banco de dados
>>> m = Enrollment(student=ful, course=c)
>>> m.save()
# Exibir algumas informações sobre a matrícula
>>> m
Out: <Enrollment: 1: fulano matriculado em Web-Dev>
# Exemplo de uso do related_name para buscar as matrículas de um estudante específico
>>> student = Student.objects.get(id=1)
>>> enrollments = student.course_enrollments.all()
>>> enrollments
Out: <QuerySet [<Enrollment: fulano matriculado em Web-Dev>]>
Agora podemos começar a construir uma aplicação em torno deste processo de usar models para interagir com um banco de dados. Vamos começar criando uma rota de índice para nosso site. Dentro de urls.py:
urlpatterns = [
path('', views.index, name="index"),
]
Dentro de views.py:
from django.shortcuts import render
from .models import Student, Course, Enrollment
# Create your views here.
def index(request):
return render(request, 'codeclass/index.html', {
"courses": Course.objects.all(),
})
Dentro de nosso novo arquivo layout.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Cursos</title>
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
Dentro de um novo arquivo index.html:
{% extends "codeclass/layout.html" %}
{% block body %}
<h1>Cursos:</h1>
<ul>
{% for course in courses %}
<li>Curso {{ course.id }}: {{ course.title }} - {{ course.description }}</li>
{% endfor %}
</ul>
{% endblock %}
O que fizemos aqui é criar uma página padrão onde temos uma lista de todos os cursos que criamos até agora. Quando abrimos a página agora, ela se parece com isso
Agora, vamos adicionar mais alguns cursos à nossa aplicação voltando ao shell do Django:
# Criando e salvando um novo curso:
>>> p = Course(title="Python-Intro", description="Welcome to python course!")
>>> p.save()
Agora, nossa página se parece com isso:
Como é muito comum que os desenvolvedores precisem criar novos objetos como fizemos no shell, o Django vem com um admin interface padrão que nos permite fazer isso mais facilmente. Para começar a usar esta ferramenta, devemos primeiro criar um usuário administrativo:
>> python manage.py createsuperuser
Username: user_a
Email address: a@a.com
Password:
Password (again):
Superuser created successfully.
Agora, devemos adicionar nossos models ao aplicativo admin entrando no arquivo admin.py dentro de nosso app, e importando e registrando nossos models. Isso diz ao Django quais models gostaríamos de ter acesso no aplicativo admin.
from django.contrib import admin
from .models import Student, Course, Enrollment
# Registrar seus models aqui.
admin.site.register(Student)
admin.site.register(Course)
admin.site.register(Enrollment)
Agora, quando visitamos nosso site e adicionamos /admin à url, podemos fazer login em uma página que se parece com isso
Depois de fazer login, você será levado a uma página como a abaixo, onde pode criar, editar e excluir objetos armazenados no banco de dados
Agora, vamos adicionar mais algumas páginas ao nosso site. Começaremos adicionando a capacidade de clicar em um curso para obter mais informações sobre ele. Para fazer isso, vamos criar um caminho de URL que inclui o id de um curso:
path("<int:course_id>", views.course, name="course")
Então, em views.py criaremos uma função course que recebe um id de curso e renderiza um novo arquivo html:
def course(request, course_id):
course = Course.objects.get(id=course_id)
return render(request, "codeclass/course.html", {
"course": course
})
Agora criaremos um template para exibir essas informações de curso com um link de volta para a página inicial
{% extends "codeclass/layout.html" %}
{% block body %}
<h1>Curso {{ course.id }}</h1>
<ul>
<li>Título: {{ course.title }}</li>
<li>Descrição: {{ course.description }}</li>
</ul>
<a href="{% url 'index' %}">Todos os cursos</a>
{% endblock %}
Finalmente, precisamos adicionar a capacidade de vincular de uma página para outra, então modificaremos nossa página de índice para incluir links:
{% extends "codeclass/layout.html" %}
{% block body %}
<h1>Cursos:</h1>
<ul>
{% for course in courses %}
<li><a href="{% url 'course' course.id %}">Curso {{ course.id }}: {{ course.title }} - {{ course.description }}</li>
{% endfor %}
</ul>
{% endblock %}
Agora nossa página inicial se parece com isso
E quando clicamos no primeiro curso, por exemplo, somos levados a esta página
Agora, iremos trabalhar com uma nova característica no Django. Para isso, adaptaremos nosso model Course para ter um outro campo:
class Course(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(blank=True)
students = models.ManyToManyField(Student, blank=True, related_name="courses")
def __str__(self):
return self.title
blank=True que significa que um curso pode não ter estudantesrelated_name que serve ao mesmo propósito que antes: nos permitirá encontrar todos os estudantes em um curso.models.py, dentro do model Course, teste passar o model para baixo da definição de Student.Para realmente fazer essas alterações, devemos fazer migrações e migrar. Como nosso modelo antigo Course não serve mais aqui, devemos tratar as remoções de cursos (o que remove as matrículas, por meio da propriedade CASCADE) Podemos então registrar o model Course em admin.py e visitar a página admin para criar alguns cursos! Obs: para remover os cursos que fizemos até agora, entre no shell e digite Course.objects.all().delete().
Agora que adicionamos os cursos de Web-Dev e Python-Intro, vamos atualizar nossa página de curso para que ela exiba todos os estudantes em um curso. Primeiro visitaremos views.py e atualizaremos nossa visualização de curso para fornecer uma lista de estudantes como contexto. Acessamos a lista usando o related_name que definimos anteriormente.
def course(request, course_id):
course = Course.objects.get(id=course_id)
students = course.students.all()
return render(request, "codeclass/course.html", {
"course": course,
"students": students
})
Agora, adicione uma lista de estudantes a course.html:
<h2>Estudantes:</h2>
<ul>
{% for student in students %}
<li>{{ student.username }}</li>
{% empty %}
<li>Nenhum estudante.</li>
{% endfor %}
</ul>
Neste ponto, quando clicamos no primeiro curso (que possui id=4 agora, pelas remoções), vemos
Agora, vamos trabalhar em dar aos visitantes do nosso site a capacidade de adicionar uma matrícula (para fins didáticos, qualquer visitante pode matricular qualquer estudante disponível, o que não é o ideal em um cenário real). Faremos isso adicionando uma rota de matrícula em urls.py:
path("<int:course_id>/enroll", views.enroll, name="enroll")
Agora, adicionaremos uma função enroll a views.py que adiciona um estudante a um curso:
def enroll(request, course_id):
# Para uma requisição post, adicionar uma
if request.method == "POST":
# Acessando o curso
course = Course.objects.get(pk=course_id)
# Encontrando o id do estudante a partir dos dados do formulário enviado
student_id = int(request.POST["student"])
# Encontrando o estudante com base no id
student = Student.objects.get(pk=student_id)
# Adicionar estudante no curso, mas relações de cursos e enrollments
course.students.add(student)
Enrollment.objects.create(student=student, course=course)
# Redirecionar usuário para a página do voo
return HttpResponseRedirect(reverse("course", args=(course.id,)))
A seguir, adicionaremos algum contexto ao nosso template de curso para que a página tenha acesso a todos que não são atualmente estudantes no curso usando a capacidade do Django de excluir certos objetos de uma consulta:
def course(request, course_id):
course = Course.objects.get(id=course_id)
students = course.students.all()
non_students = Student.objects.exclude(id__in=students.values_list('id', flat=True)).all()
return render(request, "codeclass/course.html", {
"course": course,
"students": students,
"non_students": non_students
})
Agora, adicionaremos um formulário à nossa página HTML de curso usando um campo de entrada select:
<form action="{% url 'enroll' course.id %}" method="post">
{% csrf_token %}
<select name="student" id="">
{% for student in non_students %}
<option value="{{ student.id }}">{{ student.username }}</option>
{% endfor %}
</select>
<input type="submit">
</form>
Agora, vamos ver como o site fica quando vamos para uma página de curso e adicionamos estudantes:
Outra vantagem de usar o aplicativo admin do Django é que ele é personalizável. Por exemplo, se desejarmos ver todos os aspectos de um curso na interface admin, podemos criar uma nova classe dentro de admin.py e adicioná-la como um argumento ao registrar o model Course:
class CourseAdmin(admin.ModelAdmin):
list_display = ("id", "title", "description", "students_list")
# Lista de estudantes
def students_list(self, obj):
return ", ".join([student.username for student in obj.students.all()])
students_list.short_description = "Students"
# Registrar seus models aqui.
admin.site.register(Course, CourseAdmin)
Uma coisa a se notar é que não passamos a lista de estudantes students diretamente. Como students do model é um campo Many-to-Many, não é suportado pelo list_display, visto que retorna uma QuerySet. Então, para mitigar isto, podemos criar uma função que retorna a lista de estudantes em uma string.
Também há outras coisas que podemos definir, como limite de estudantes na string, etc.
Agora, quando visitamos a página admin para cursos, podemos ver as mudanças:
Confira a documentação do admin do Django para encontrar mais maneiras de personalizar o aplicativo admin.
A última coisa que discutiremos na aula de hoje é a ideia de autenticação, ou permitir que os usuários façam login e logout de um site. Felizmente, o Django torna isso muito fácil para nós, então vamos passar por um exemplo de como faríamos isso. Começaremos criando um novo app chamado users. Aqui passaremos por todas as etapas normais de criação de um novo app, mas em nosso novo arquivo urls.py, adicionaremos mais algumas rotas:
urlpatterns = [
path('', views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout")
]
Vamos começar criando um formulário onde um usuário pode fazer login. Criaremos um arquivo layout.html como sempre, e então criaremos um arquivo login.html que contém um formulário e que exibe uma mensagem se existir.
{% extends "users/layout.html" %}
{% block body %}
{% if message %}
{{ message }}
{% endif %}
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<input type="text", name="username", placeholder="Nome de Usuário">
<input type="password", name="password", placeholder="Senha">
<input type="submit", value="Login">
</form>
{% endblock %}
Agora, em views.py, adicionaremos três funções:
def index(request):
# Se nenhum usuário estiver logado, retornar à página de login:
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse("login"))
return render(request, "users/user.html")
def login_view(request):
return render(request, "users/login.html")
def logout_view(request):
# Pass é uma maneira simples de dizer ao python para não fazer nada.
pass
A seguir, podemos ir para o site admin e adicionar alguns usuários. Depois de fazer isso, voltaremos para views.py e atualizaremos nossa função login_view para lidar com uma requisição POST com nome de usuário e senha:
# Importações adicionais que precisaremos:
from django.contrib.auth import authenticate, login, logout
def login_view(request):
if request.method == "POST":
# Acessando nome de usuário e senha dos dados do formulário
username = request.POST["username"]
password = request.POST["password"]
# Verificar se nome de usuário e senha estão corretos, retornando objeto User se sim
user = authenticate(request, username=username, password=password)
# Se um objeto user for retornado, fazer login e rotear para a página inicial:
if user:
login(request, user)
return HttpResponseRedirect(reverse("index"))
# Caso contrário, retornar à página de login novamente com novo contexto
else:
return render(request, "users/login.html", {
"message": "Credenciais Inválidas"
})
return render(request, "users/login.html")
Agora, criaremos o arquivo user.html que a função index renderiza quando um usuário está autenticado:
{% extends "users/layout.html" %}
{% block body %}
<h1>Bem-vindo, {{ request.user.first_name }}</h1>
<ul>
<li>Nome de Usuário: {{ request.user.username }}</li>
<li>Email: {{ request.user.email }}</li>
</ul>
<a href="{% url 'logout' %}">Sair</a>
{% endblock %}
Finalmente, para permitir que o usuário saia, atualizaremos a função logout_view para que ela use a função logout integrada do Django:
def logout_view(request):
logout(request)
return render(request, "users/login.html", {
"message": "Deslogado"
})
Agora que terminamos, aqui está uma demonstração do site
Isso é tudo para esta aula!