Entendendo Django

Projeto

"Projeto" é como Django chama a base do sistema.

Criado com django-admin startproject [PROJECTNAME] ..

Projeto: startproject


    .
    ├── exemplo
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── manage.py
                        

Projeto > App

Dentro do projeto, para criar um app:


cd exemplo
django-admin startapp [app]
                        

Projeto > 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
                        

Outras coisas do manage.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.

Projeto > App > models.py

Definição do banco de dados para o app.


class Product(models.Model):

    """Product information"""

    name = models.CharField('Name', max_length=40)
                        

Projeto > App > admin.py

Definição do admin (ou não) dos models.


class ProductAdmin(admin.ModelAdmin):
    pass

admin.site.register(Product, ProductAdmin)
                        

Projeto > App > forms.py

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
                        

O "label" do campo ficará com o valor do primeiro parâmetro usado no field em ModelForms.

Projeto > App > views.py

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})
                        

Projeto > App > templates/product_info.html

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.

Projeto > App > urls.py

URLs internas do app.


from . import views

urlpatterns = [
    url(r'^(?P<product_id>[0-9]+)/$', views.get_product, name='get'),

]
                        

App completa!

... só que o Django ainda não sabe que ela existe.

Projeto > settings.py


[...]
INSTALLED_APPS = (
    [...]
    'products',
    [...]
)
                        

Agora o Django sabe que a App existe!

Só não sabe como chegar lá porque faltam as URLs.

Projeto > urls.py


[...]
urlpatterns = [
    [...]
    url(r'/products', include('products')),
    [...]
]
                        

Agora funciona como esperado:

  • Request de um browser chega no Django;
  • Consulta o urls.py base do projeto para encontrar o que será executado;
  • Consulta o urls.py da app (no caso) para encontrar a view que será executada;
  • Encontra a view dentro da app, que busca as informações no model;
  • Renderiza o template;
  • Retorna o template renderizado para o usuário.
Go deeper

Índices

Cada campo pode ser indexado usando a propriedade db_index.


class Order(models.Model):

    """An order."""

    costumer.models.ForeignKey(User, db_index=True)
                        

Meta

Models podem ter uma classe de "Meta" com informações adicionais:


class Order(models.Model):
    
    """An order."""

    costumer = models.ForeignKey(User, db_index=True)
    date = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'my_orders'
        index_together = [
            ('costumer', 'date')
        ]
                        

Relationships

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)
                        

"OneToMany"

Não existe relacionamento "one to many" (p.ex., um pedido tem vários ítens de pedido). Para isso, usa-se o ForeignKey na parte "many".


class Order(models.Model):

    """An order."""

    costumer = models.ForeignKey(Users)


class OrderItem(models.Model):

    """An item of an order."""

    order = models.ForeignKey(Order))metro
    product = models.ForeignKey(Product)
    quantity = models.PositiveIntegerField()
                        

Inserts


product = Product(name='fruit')
order = Order()
order.products.add(product)
product.save()
order.save()
                    

Queries


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()
                        

Queries

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).

Reverse Queries

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 com AND

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)
                        

Queries com OR

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

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.

CHOICES

É 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.

CHOICES

Para as queries, é preciso pedir o valor do campo, não do display.


credit_card_orders = Order.objects.filter(payment_type='C')
                        

CHOICES

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

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

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']
                        

Fixtures em Produção

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

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):
    # ...
                        

Signals

  • sender = o model sendo afetado.
  • instance = registro sendo alterado.
  • created = se é um novo registro ou não.
  • raw = registro salvo exatamente como indicado (fixtures).
  • using = alías do database sendo usado.
  • update_fields = campos sendo salvos no save() (None se forem todos).

Mais forms


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})
                        

Mais forms

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'
                        

Mais forms

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']}
                        

Templatetags

Funções especiais para templates para apresentação de valores.

Projeto > App > templatetags/

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).

Projeto > App > templatetags/ filters.py


def free(value):
    if value == 0:
        return _('Free')
    return value
                        

Projeto > App > templates/ product_info.html


{{ product.price|free }}
                        

URLs/Redirects

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'))

                        

Testes

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):

  • Modelos;
  • Views;
  • Requisições inteiras.

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')