"Projeto" é como Django chama a base do sistema.
Criado com django-admin startproject [PROJECTNAME] .
.
.
├── exemplo
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
Dentro do projeto, para criar um app:
cd exemplo
django-admin startapp [app]
.
├── manage.py
└── exemplo
├── __init__.py
├── products
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── settings.py
├── urls.py
└── wsgi.py
python manage.py syncdb
: Cria as
tabelas necessárias e adiciona os dados
necessários.python manage.py makemigrations
:
Verifica alterações em modelos e gera regras
para conversão do banco.python manage.py shell
: Shell de
acesso ao Django (na verdade, abre um
interpretador Python com um monte de coisas já
carregadas/configuradas)python manage.py runserver
: Roda
um server de desenvolvimento.Definição do banco de dados para o app.
class Product(models.Model):
"""Product information"""
name = models.CharField(max_length=40)
Definição do admin (ou não) dos models.
class ProductAdmin(admin.ModelAdmin):
pass
admin.site.register(Product, ProductAdmin)
Definição de formulários/formulários baseados em models.
class ProductForm(forms.Form):
name = forms.CharField(label='Your name', max_length=40)
class ProductForm(ModelForm):
class Meta:
model = Product
Views do app.
def get_product(request, product_id):
if request.method == 'GET':
product = get_object_or_404(Product, pk=product_id)
return render(request,
'product_info.html',
{'product': product})
else:
form = ProductForm(request)
if form.is_valid():
form.save()
return render(request,
'product_info.html',
{'form': form})
Na verdade, todos os templates da app ficam em
templates
.
{% extends 'base.html' %}
{% if product %}
- Product name: {{ product.name }}
{% endif %}
{% if form %}
{{ form }}
{% endif %}
O base.html
pode estar no templates
do projeto.
URLs internas do app.
from . import views
urlpatterns = [
url(r'^(?P<product_id>[0-9]+)/$', views.get_product, name='get'),
]
... só que o Django ainda não sabe que ela existe.
[...]
INSTALLED_APPS = (
[...]
'products',
[...]
)
Agora o Django sabe que a App existe!
Só não sabe como chegar lá porque faltam as URLs.
[...]
urlpatterns = [
[...]
url(r'/products', include('products')),
[...]
]
Agora funciona como esperado:
Projeto > App > models.py
class Product(models.Model):
"""Product information"""
name = models.CharField(max_length=40)
price = models.DecimalField(max_digits=6, decimal_places=2)
class Order(models.Model):
"""An order."""
products = models.ManyToManyField(Product)
product = Product(name='fruit')
order = Order()
order.products.add(product)
product.save()
order.save()
all_products = Product.objects.all()
fruit = Product.objects.get(pk=1)
fruit = Product.objects.get(name='fruit')
all_fruits_in_orders = Order.objects \
.filter(products__name__like='fruit') \
.distinct()
get
só pode retornar um elemento.
pk
é uma variável mágica que aponta
para o campo marcado como
primary_key=True
; se não houver um
primary_key
, o Django cria um
IntegerField(auto_increment=True)
.
Quando é criada uma relação, o Django cria também uma relação reversa entre os models.
order = Order.objects.get(pk=1)
print order.products.all()
print Products.order_set.all()
O nome da relacionamento reverso pode ser alterado com
related_name
.
Queries onde todas as condições devem ser
satisfeitas podem ser feitas passando mais
parametros em filter
.
all_expensive_fruits = Order.objects.filter(
products__name__like='fruit',
price__gt=1000)
Para queries com OR, deve-ser usar o objeto Q
.
all_fruits_or_expensive = Order.objects.filter(
Q(products__name__like='fruit') |
Q(price__gt=1000))
Q
também pode ser usado para AND e queries mais
complexas.
fruit_salad = Order.objects.filter(
Q(products__name__like='fruit') &
(Q(price__gt=1000) | Q(price=0))
É possível misturar Q
com filter normal, mas existe uma questão de
prioridades e é melhor nem pensar em misturar os
dois.
É possível definir valores fixos para campos com
choices
.
class Order(models.Model):
"""An order."""
CHOICES = (
('M', 'Money'),
('C', 'Credit card')
)
products = models.ManyToManyField(Product)
payment_type = models.CharField(max_length=1, choices=CHOICES)
O primeiro valor é o valor que será registrado no banco; o segundo, apresentado num ModelForm.
Para as queries, é preciso pedir o valor do campo, não do display.
credit_card_orders = Order.objects.filter(payment_type='C')
Para facilitar a vida, usar contantes.
class Order(models.Model):
"""An order."""
MONEY = 'M'
CREDIT_CARD = 'C'
CHOICES = (
(MONEY, 'Money'),
(CREDIT_CARD, 'Credit card')
)
credit_card_orders = Order.objects.filter(
payment_type=Order.CREDIT_CARD)
Fixtures são arquivos JSON que o Django consegue usar para preencher o banco de dados.
[
{
"pk": 1,
"model": "Products",
"fields": {
"name": "fruit"
}
}
]
Fixtures em testes se aplicam a suíte inteira.
Para definir que um teste usa fixtures, é usada a
variável fixtures
da classe.
class ProductTest(StaticLiveServerTestCase):
fixtures = ['products.json']
Para criação das tabelas de banco de dados, usa-se
python manage.py syncdb
.
Se houverem fixtures a serem carregadas, essas serão
injetadas no banco de dados durante o
syncdb
.
Signals (sinais) são eventos gerados dentro do Django para chamar funções de usuário em algumas condições (normalmente relacionadas com models).
from django.db.models.signals import pre_save
from django.dispatch import receiver
from models import Product
@receiver(post_save, sender=Product)
def after_saving_product(sender, instance, created, raw, using, update_fields):
# ...
save()
(None se forem todos).
class ProductForm(forms.Form):
name = forms.CharField(label='Your name', max_length=40)
Para usar o name:
def get_product(request, product_id):
if request.method == 'POST':
form = ProductForm(request)
if form.is_valid():
record = Product(name=form.cleaned_data['name'])
record.save()
return render(request,
'product_info.html',
{'form': form})
cleaned_data
é usado porque os campos podem ser alterados
para evitar problemas dentro do sistema.
Regras de "limpeza" estão definidas dentro dos FormFields.
f = forms.CharField()
f.clean(True)
'True'
Em caso de erro, isso é indicado no form, para cada campo e um dicionario global com essa informação:
form = ProductForm({'name': 'nome muito grande, com mais de 40 caracteres, o que é inválido'})
f['name'].errors # [u'Too many characters']
f.errors # {'name': [u'Too many characters']}
Funções especiais para templates para apresentação de valores.
Dentro do diretório templatetags
do App
ficam os módulos com os tags.
(Lembrar de colocar o arquivo __init__.py
para que
o Python detecte o diretório como um módulo).
def free(value):
if value == 0:
return _('Free')
return value
{{ product.price|free }}
Redirects servem para indicar que o browser deve se direcionar à outra URL.
Para referenciar URLs de outras apps (ou mesmo da
mesma app), deve-ser usar o método
reverse
.
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
def view(request):
# do stuff
return redirect(reverse('app:func'))
Bem parecido com o módulo unittest
do Python
padrão.
import random
from django.test import TestCase
class RandomTest(TestCase):
def test_random(self):
"""Check if random numbers return random numbers."""
self.assertTrue(random.randint(255) != 0)
Testes podem acessar qualquer coisa (desde que importados):
from django.test import Client
from django.test import TestCase
class RequestTest(TestCase):
def test_retrieve_page(self):
client = Client()
response = client.get('/')
self.assertTrue('success!' in response.content)
self.assertEquals(response.status_code, 200)
self.assertTrue('variable' in response.context)
self.assertEqual(response.context['variable'], 'success')