# Dica 1 - Django boilerplate e cookiecutter-django

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/OYcOpcPcp8Y)

**Importante:** nas próximas dicas, troque

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Ou seja, remova a `\` no meio das tags.

[django-boilerplate](https://github.com/rg3915/django-boilerplate)

[boilerplate2.sh](https://gist.github.com/rg3915/a264d0ade860d2f2b4bf)

[cookiecutter-django](https://github.com/pydanny/cookiecutter-django)

```
python -m venv .venv
source .venv/bin/activate

pip install "cookiecutter>=1.7.0"
cookiecutter https://github.com/pydanny/cookiecutter-django
pip install -r requirements/local.txt 
python manage.py migrate

createdb myproject -U postgres

python manage.py migrate
```


# Dica 1.1 - Django boilerplate

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/eLKjL61HEbQ)

Novo boilerplate com Django 4.0.2

<https://github.com/rg3915/django-boilerplate>


# Dica 2 - Django extensions

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/qUKzDSSuh2w)

<https://django-extensions.readthedocs.io/en/latest/index.html>

```
pip install django-extensions
```

```python
# settings.py
INSTALLED_APPS = (
    ...
    'django_extensions',
)
```

```
python manage.py show_urls
```

```
python manage.py shell_plus
```


# Dica 3 - Django bulk\_create e django-autoslug

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/Py-AG6S_vJI)

### python-slugify

<https://pypi.org/project/python-slugify/>

```python
from slugify import slugify

text = 'Dicas de Django'
print(slugify(text))
url = f'example.com/{slugify(text)}'
```

#### bulk-create

<https://docs.djangoproject.com/en/3.0/ref/models/querysets/#bulk-create>

#### django-autoslug

<https://pypi.org/project/django-autoslug/>

```
pip install django-autoslug
```

#### models.py

```python
from autoslug import AutoSlugField
from django.db import models


class Article(models.Model):
    title = models.CharField('título', max_length=200)
    subtitle = models.CharField('sub-título', max_length=200)
    slug = AutoSlugField(populate_from='title')
    category = models.ForeignKey(
        'Category',
        related_name='categories',
        verbose_name='categoria',
        on_delete=models.SET_NULL,
        null=True,
        blank=True
    )
    published_date = models.DateTimeField(
        'criado em',
        auto_now_add=True,
        auto_now=False
    )

    class Meta:
        ordering = ('title',)
        verbose_name = 'artigo'
        verbose_name_plural = 'artigos'

    def __str__(self):
        return self.title


class Category(models.Model):
    title = models.CharField('título', max_length=50, unique=True)

    class Meta:
        ordering = ('title',)
        verbose_name = 'categoria'
        verbose_name_plural = 'categorias'

    def __str__(self):
        return self.title
```

#### shell\_plus

```python
categories = [
    'dicas',
    'django',
    'python',
]

aux = []

for category in categories:
    obj = Category(title=category)
    aux.append(obj)

Category.objects.bulk_create(aux)


titles = [
    {
        'title': 'Django Boilerplate',
        'subtitle': 'Django Boilerplate',
        'category': 'dicas'
    },
    {
        'title': 'Django extensions',
        'subtitle': 'Django extensions',
        'category': 'dicas'
    },
    {
        'title': 'Django Admin',
        'subtitle': 'Django Admin',
        'category': 'admin'
    },
    {
        'title': 'Django Autoslug',
        'subtitle': 'Django Autoslug',
        'category': 'dicas'
    },
]

aux = []

for title in titles:
    category = Category.objects.filter(title=title['category']).first()
    article = dict(
        title=title['title'],
        subtitle=title['subtitle']
    )
    if category:
        obj = Article(category=category, **article)
    else:
        obj = Article(**article)
    aux.append(obj)

Article.objects.bulk_create(aux)
```


# Dica 4 - Django Admin personalizado

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/jogkxIkCzI8)

<https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#modeladmin-options>

#### admin.py

```python
from django.conf import settings
from django.contrib import admin

from .models import Article, Category

# from .forms import ArticleAdminForm


@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'get_published_date')
    search_fields = ('title',)
    list_filter = (
        'category',
    )
    readonly_fields = ('slug',)
    date_hierarchy = 'published_date'
    # form = ArticleAdminForm

    def get_published_date(self, obj):
        if obj.published_date:
            return obj.published_date.strftime('%d/%m/%Y')

    get_published_date.short_description = 'Data de Publicação'


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    actions = None

    def has_add_permission(self, request, obj=None):
        return False

    if not settings.DEBUG:
        def has_delete_permission(self, request, obj=None):
            return False
```


# Dica 5 - Django Admin Date Range filter

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/s5QzePekrvQ)

<https://github.com/tzulberti/django-datefilterspec>

```
pip install django-daterange-filter
```

```python
# settings.py
INSTALLED_APPS = (
    ...
    'daterange_filter'
)
```

```python
# admin.py
from daterange_filter.filter import DateRangeFilter

...

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    ...
    list_filter = (
        ('published_date', DateRangeFilter),
        'category',
    )
```


# Dica 6 - Geradores de senhas randômicas - uuid, hashids, secrets

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/-3znAePkMqY)

## 6.1 - uuid

<https://docs.python.org/3/library/uuid.html>

```python
import uuid

uuid.uuid4()

uuid.uuid4().hex
```

```python
# models.py
import uuid

from django.db import models


class UuidModel(models.Model):
    slug = models.UUIDField(unique=True, editable=False, default=uuid.uuid4)

    class Meta:
        abstract = True

class Category(UuidModel):
    title = models.CharField('título', max_length=50, unique=True)
    ...
```

## 6.2 - shortuuid

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/qsRefchlXlo)

<https://pypi.org/project/shortuuid/>

```
pip install shortuuid
```

```python
>>> import shortuuid
>>> 
>>> shortuuid.uuid()
'823MMBZx7LNEnnPBtCAorG'
>>> 
>>> shortuuid.uuid(name='example.com')
'exu3DTbj2ncsn9tLdLWspw'
>>> 
>>> shortuuid.ShortUUID().random(length=22)
'4CHN7TshKtrVnW4KLgVMhY'
>>> 
>>> shortuuid.set_alphabet('regis')
>>> shortuuid.uuid()
'gerigigiesreissgisrsggrrseieereggierrgreriissreiiisiegrr'
>>> 
```

## 6.3 - hashids

<https://gist.github.com/rg3915/4684721a603cf6d0dd3b9495744482fe>

<https://pypi.org/project/hashids/>

```
pip install hashids
```

```python
from hashids import Hashids

hashids = Hashids()

>>> hashids.encode(42)
'9x'
>>> hashids.decode('9x')
(42,)
>>> hashids.encode(665190)
'k7qWJ'
>>> hashids.decode('k7qWJ')
(665190,)
>>> hashids.encode(1122, 4200, 32665)
'ELmhW0mFD7o'
>>> hashids.decode('ELmhW0mFD7o')
(1122, 4200, 32665)
>>> hashids = Hashids(alphabet='abcdefghijklmnopqrstuvwxyz1234567890', min_length=22)
>>> for i in range(10): hashids.encode(i)
... 
'9xkwnvoj3ejwgp6481y5mq'
'ml6kz731jdkoe524rxn0yq'
'kwp7yx456gl9g91lm23v8n'
'0qr6jxo9memje214w8zlvp'
'9poy2jq1xdn0e037nwv4zl'
'nz97pw01jgo5el24yrxv6m'
'q4pkmy631epjenrxv70w5l'
'n97kyw8q0dq9eo143z2x6v'
'x7n4zl0pkgr4d6o3vq92wy'
'6y27mjnzkev3d3549vq0xl'
>>> 
```

### django-hashid-field

<https://pypi.org/project/django-hashid-field/>

```
pip install django-hashid-field==3.1.3
```

```python
# models.py
from hashid_field import HashidAutoField


class Article(models.Model):
    id = HashidAutoField(primary_key=True)
    ...
```

```python
# python manage.py shell_plus
from hashid_field import Hashid

articles = Article.objects.all()
for article in articles:
    hashid = Hashid(article.id)
    print(article.id, hashid.id)
```

<https://www.howtogeek.com/howto/30184/10-ways-to-generate-a-random-password-from-the-command-line/>

### Gerando senhas no terminal Linux

```
date +%s | sha256sum | base64 | head -c 32 ; echo

openssl rand -base64 32

strings /dev/urandom | grep -o '[[:alnum:]]' | head -n 30 | tr -d '\n'; echo

date | md5sum
```

```
# apt install -y gpw

gpw

gpw 3 32

gpw 1 5
```

### Gerando senhas com Python

#### com random

```python
import random

chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()?"
size = 8
secret_key = "".join(random.sample(chars,size))
print(secret_key)
```

#### com random e string

<https://pynative.com/python-generate-random-string/>

```python
# Generate a random string of specific letters only

import random
import string


def randString(length=5):
    # put your letters in the following string
    your_letters='abcdefghi'
    return ''.join((random.choice(your_letters) for i in range(length)))

print("Random String with specific letters ", randString())
print("Random String with specific letters ", randString(5))
```

#### com secrets

<https://docs.python.org/3/library/secrets.html>

*New in Python 3.6*

```python
import secrets

secrets.token_hex(16)

secrets.token_urlsafe(16)

url = 'https://mydomain.com/reset=' + secrets.token_urlsafe()
```

### Django

```
vim contrib/env_gen.py
```

```python
"""
Django SECRET_KEY generator.
"""
from django.utils.crypto import get_random_string

chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'

CONFIG_STRING = """
DEBUG=True
SECRET_KEY=%s
ALLOWED_HOSTS=127.0.0.1, .localhost
""".strip() % get_random_string(50, chars)

print(CONFIG_STRING)

# Writing our configuration file to '.env'
with open('.env', 'w') as configfile:
    configfile.write(CONFIG_STRING)
```

```
python contrib/env_gen.py
```


# Dica 7 - Rodando o ORM do Django no Jupyter Notebook

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/bXtmvu_O_sk)

Instale

```
pip install ipython[notebook]
```

Rode

```
python manage.py shell_plus --notebook
```

**Obs**: No Django 3.x talvez você precise dessa configuração [async-safety](https://docs.djangoproject.com/en/3.0/topics/async/#async-safety).

`os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"`


# Dica 8 - Conhecendo o Django Debug Toolbar

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/T23bEwMhD6A)

<https://django-debug-toolbar.readthedocs.io/en/latest/>

```
pip install django-debug-toolbar
```

#### Configurando o `settings.py`

```python
INSTALLED_APPS = [
    # ...
    'django.contrib.staticfiles',
    # ...
    'debug_toolbar',
]

MIDDLEWARE = [
    # ...
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # Deve estar por último.
]

INTERNAL_IPS = [
    # ...
    '127.0.0.1',
    # ...
]

STATIC_URL = '/static/'
```

#### Configurando o `urls.py`

```python
from django.conf import settings
from django.urls import include, path

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns
```


# Dica 9 - Escondendo suas senhas python-decouple

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/eOwN7e0QBXo)

[Video do Henrique Bastos na Live de Python #97](https://www.youtube.com/watch?v=zYJGpLw5Wv4)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://www.youtube.com/watch?v=zYJGpLw5Wv4)

<https://github.com/henriquebastos/python-decouple>

```
pip install python-decouple
```

Crie um arquivo `.env` com o seguinte conteúdo (de exemplo)

```
DEBUG=True
SECRET_KEY=c9^3g^bn6wgo8tabf*dl$@vx@m-!9ux%*9)88qnun&hk++sa90
ALLOWED_HOSTS=127.0.0.1,.localhost
POSTGRES_DB=mydb
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypass
DB_HOST=localhost

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=

# console ou smtp
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
EMAIL_HOST=smtp.sendgrid.net
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
```

repare que não deve haver espaços e nem aspas.

E em `settings.py` faça

```python
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())

EMAIL_BACKEND = config('EMAIL_BACKEND')
EMAIL_HOST = config('EMAIL_HOST')
EMAIL_PORT = config('EMAIL_PORT')
EMAIL_USE_TLS = config('EMAIL_USE_TLS')
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config('POSTGRES_DB'),
        'USER': config('POSTGRES_USER'),
        'PASSWORD': config('POSTGRES_PASSWORD'),
        'HOST': config('DB_HOST', 'localhost'),
        'PORT': '5432',
    }
}
```


# Dica 10 - Prototipagem de web design (Mockup)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/Ypbj_d1oGuY)

[excalidraw.com](https://excalidraw.com/)

[moqups.com](https://moqups.com/)

[balsamiq.com](https://balsamiq.com/)

[marvelapp.com](https://marvelapp.com/)

[mockflow.com](https://www.mockflow.com/)


# Dica 11 - Bootstrap e Bulma + Colorlib

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/J86_rp0ibGI)

[getbootstrap.com](https://getbootstrap.com/)

[getbootstrap.com/docs/4.5/examples](https://getbootstrap.com/docs/4.5/examples/)

[bulma.io](https://bulma.io/)

[bulmatemplates.github.io/bulma-templates](https://bulmatemplates.github.io/bulma-templates/)

[bulmathemes.com](https://bulmathemes.com/)

[colorlib.com](https://colorlib.com/)


# Dica 12 - Imagens: pexels e unsplash

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/g95YG5RGGmE)

[pexels.com](https://www.pexels.com/pt-br/)

[unsplash.com](https://unsplash.com/)


# Dica 13 - Cores

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/EcwxPzgwE4I)

[color.adobe.com/pt/create/color-wheel](https://color.adobe.com/pt/create/color-wheel)

[coolors.co](https://coolors.co/)

[materialuicolors.co](https://materialuicolors.co/)

[htmlcolorcodes.com](https://htmlcolorcodes.com/)

[clrs.cc](http://clrs.cc/)


# Dica 14 - Herança de Templates e Arquivos estáticos

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/wSfuzUVnuzw)

[Video Introdução a Arquitetura do Django - Pyjamas 2019](https://www.youtube.com/watch?v=XjXpwZhOKOs)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://www.youtube.com/watch?v=XjXpwZhOKOs)

![diagrama](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c683a3f911e06051ab380d353831387c24041452%2Ftemplates-diagram.png?alt=media\&token=9ec8fcfd-b9d6-4f97-949a-c012d0651be9)

![templates-pyjamas](https://raw.githubusercontent.com/rg3915/pyjamas2019-django/master/img/final.png)


# Dica 15 - Busca por data no frontend

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/sqhQM5KUFHE)

Considere um template com os campos:

```html
<input class="form-control" name='start_date' type="date">
<input class="form-control" name='end_date' type="date">
```

Em `views.py` basta fazer:

```python
def article_list(request):
    template_name = 'core/article_list.html'
    object_list = Article.objects.all()

    start_date = request.GET.get('start_date')
    end_date = request.GET.get('end_date')

    if start_date and end_date:
        # Converte em data e adiciona um dia.
        end_date = parse(end_date) + timedelta(1)
        object_list = object_list.filter(
            published_date__range=[start_date, end_date]
        )

    context = {'object_list': object_list}
    return render(request, template_name, context)
```

Pra não precisar fazer o

```python
end_date = parse(end_date) + timedelta(1)
```

basta acrescentar `date` antes do `range`, dai fica assim:

```python
object_list = object_list.filter(
    published_date__date__range=[start_date, end_date]
)
```

Agradecimentos a [@walisonfilipe](https://twitter.com/walisonfilipe)


# Dica 16 - Filtros com django-filter

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/LZJjSeJC09A)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Instale o [django-filter](https://django-filter.readthedocs.io/en/stable/)

```
pip install django-filter
```

Acrescente-o ao `INSTALLED_APPS`

```python
INSTALLED_APPS = [
    ...
    'django_filters',
]
```

Crie um arquivo `filters.py`

```python
import django_filters

from .models import Article


class ArticleFilter(django_filters.FilterSet):
    title = django_filters.CharFilter(lookup_expr='icontains')
    subtitle = django_filters.CharFilter(lookup_expr='icontains')

    class Meta:
        model = Article
        fields = ('title', 'subtitle')
```

Em `views.py`

```python
from .filters import ArticleFilter


def article_list(request):
    template_name = 'core/article_list.html'
    object_list = Article.objects.all()
    article_filter = ArticleFilter(request.GET, queryset=object_list)

    ...

    context = {
        'object_list': article_filter,
        'filter': article_filter
    }
    return render(request, template_name, context)
```

Em `article_list.html`

```html
  <div class="row">
    <div class="col-md-4">
      <form method="GET">
        {{ filter.form.as_p }}
        <input type="submit" />
      </form>
    </div>

    <div class="col-md-8">
      <table class="table">
        <thead>
          <tr>
            <th>Título</th>
            <th>Sub-título</th>
            <th>Data de publicação</th>
          </tr>
        </thead>
        <tbody>
          {\% for obj in filter.qs %}
            <tr>
              <td>{{ obj.title }}</td>
              <td>{{ obj.subtitle }}</td>
              <td>{{ obj.published_date }}</td>
            </tr>
          {\% endfor %}
        </tbody>
      </table>
    </div>
  </div>
```


# Dica 17 - Criando comandos personalizados

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/tqr23jPrqrw)

Baseado em [Criando novos comandos no django-admin](http://pythonclub.com.br/criando-novos-comandos-no-django-admin.html) e na [Live 95 do Edu Live de Python](https://youtu.be/cyxky2QJlwg?t=3482).

### Criando as pastas

Para criarmos um novo comando precisamos das seguintes pastas:

```
core
├── management
│   ├── __init__.py
│   ├── commands
│   │   ├── __init__.py
│   │   ├── novocomando.py
```

No nosso caso, teremos 2 novos comandos, então digite, estando na pasta myproject

```
mkdir -p core/management/commands
touch core/management/__init__.py
touch core/management/commands/{__init__.py,hello.py,search.py}
```

```python
# hello.py
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = 'Print hello world.'

    def add_arguments(self, parser):
        # Argumento nomeado (opcional)
        parser.add_argument(
            '--awards', '-a',
            action='store_true',
            help='Ajuda da opção aqui.'
        )

    def handle(self, *args, **options):
        self.stdout.write('Hello world.')
        if options['awards']:
            self.stdout.write('Awards')
```

```python
# search.py
from django.core.management.base import BaseCommand

from myproject.core.models import Article


class Command(BaseCommand):
    help = """Localiza um artigo pelo título ou sub-título."""

    def add_arguments(self, parser):
        parser.add_argument(
            '--title', '-t',
            dest='title',
            default=None,
            help='Localiza um artigo pelo título.'
        )
        parser.add_argument(
            '--subtitle', '-sub',
            dest='subtitle',
            default=None,
            help='Localiza um artigo pelo sub-título.'
        )

    def handle(self, title=None, subtitle=None, **options):
        """ dicionário de filtros """
        self.verbosity = int(options.get('verbosity'))

        filters = {
            'title__icontains': title,
            'subtitle__icontains': subtitle,
        }

        filter_by = {
            key: value for key,
            value in filters.items() if value is not None
        }
        queryset = Article.objects.filter(**filter_by)

        if self.verbosity > 0:
            for article in queryset:
                self.stdout.write("{0} {1}".format(
                    article.title, article.subtitle))
            self.stdout.write(f'\n{queryset.count()} artigos localizados.')
```


# Dica 18 - bulk\_create e bulk\_update

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/U99VT4UwJ5k)

## bulk\_create

O [bulk\_create](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#bulk-create) serve para inserir uma grande quantidade de dados num banco de forma super rápida.

Vamos usar o

`python manage.py shell_plus`

Primeiro vamos criar uns dados aleatórios

```python
import secrets
import string

N = 12
list_items = []

for i in range(100):
    res = ''.join(secrets.choice(string.ascii_lowercase) for i in range(N))
    list_items.append(res)
```

Agora vamos inserir os dados com bulk\_create

```python
aux = []
for item in list_items:
    obj = Article(title=item, subtitle=item)
    aux.append(obj)

Article.objects.bulk_create(aux)
```

## bulk\_update

Como o nome já diz, o [bulk\_update](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#bulk-update) serve para atualizar os dados.

```python
articles = Article.objects.all()
category = Category.objects.first()
for article in articles:
    article.category = category

Article.objects.bulk_update(articles, ['category'])
```


# Dica 19 - Criando Issues por linha de comando com a api do github

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/XwT2CMrGfiE)

## [github cli](https://docs.github.com/en/rest/reference/issues#create-an-issue)

`pip install requests`

```python
import json

import requests
from decouple import config

# Autenticação
REPO_USERNAME = config('REPO_USERNAME')
REPO_PASSWORD = config('REPO_PASSWORD')

# O repositório para adicionar a issue
REPO_OWNER = config('REPO_OWNER')
REPO_NAME = config('REPO_NAME')


def make_github_issue(title, body=None, assignee=None, milestone=None, labels=None):
    '''
    Create an issue on github.com using the given parameters.
    '''
    url = 'https://api.github.com/repos/%s/%s/issues' % (REPO_OWNER, REPO_NAME)
    session = requests.Session()
    session.auth = (REPO_USERNAME, REPO_PASSWORD)
    # Create our issue
    issue = {
        'title': title,
        'body': body,
        'assignee': assignee,
        'milestone': milestone,
        'labels': labels
    }
    # Add the issue to our repository
    r = session.post(url, json.dumps(issue))
    if r.status_code == 201:
        print('Successfully created Issue "%s"' % title)
    else:
        print('Could not create Issue "%s"' % title)
        print('Response:', r.content)


if __name__ == '__main__':
    title = 'Criar github cli'
    body = 'API para criar issues por linha de comando.'
    make_github_issue(
        title=title,
        body=body,
        assignee='rg3915',
        milestone=None,
        labels=['enhancement']
    )
```


# Dica 20 - api github e click

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/gwYpMKDAqBM)

`pip install click`

```python
import json

import click
import requests
from decouple import config

'''
https://docs.github.com/en/rest/reference/issues#create-an-issue

Usage: python github_cli2.py --title='Your title' \
            --body='Your description' \
            --assignee='Assignee name' \
            --labels='enhancement'
'''

# Autenticação
REPO_USERNAME = config('REPO_USERNAME')
REPO_PASSWORD = config('REPO_PASSWORD')

# O repositório para adicionar a issue
REPO_OWNER = config('REPO_OWNER')
REPO_NAME = config('REPO_NAME')


@click.command()
@click.option('--title', prompt='Title', help='Type the title.')
@click.option('--body', prompt='Description', help='Type the description.')
@click.option('--assignee', prompt='Assignee', help='Type the assignee name.')
@click.option('--labels', prompt='Labels', help='Type the labels.')
def make_github_issue(title, body=None, assignee=None, milestone=None, labels=None):
    '''
    Create an issue on github.com using the given parameters.
    '''
    url = 'https://api.github.com/repos/%s/%s/issues' % (REPO_OWNER, REPO_NAME)
    session = requests.Session()
    session.auth = (REPO_USERNAME, REPO_PASSWORD)
    # Create our issue
    issue = {
        'title': title,
        'body': body,
        'assignee': assignee,
        'milestone': milestone,
        'labels': [labels]
    }
    # Add the issue to our repository
    r = session.post(url, json.dumps(issue))
    if r.status_code == 201:
        print('Successfully created Issue "%s"' % title)
    else:
        print('Could not create Issue "%s"' % title)
        print('Response:', r.content)


if __name__ == '__main__':
    make_github_issue()
```

Como usar

```
python github_cli2.py --title='Your title' \
    --body='Your description' \
    --assignee='username' \
    --labels='enhancement'
```


# Dica 21 - Criando issues por linha de comando com gitlab cli

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/mZezRjHv4Xg)

Doc: <https://python-gitlab.readthedocs.io/en/stable/>

## Configuração

Primeiro precisamos criar um arquivo `/etc/myfile.cfg`

`sudo vim /etc/myfile.cfg # precisa do sudo`

```
[global]
default = somewhere
ssl_verify = true
timeout = 5

[somewhere]
url = https://gitlab.com
private_token = your-token
api_version = 4
```

## Instalação

`pip install python-gitlab`

## Fazendo um teste no Python

```python
import gitlab

gl = gitlab.Gitlab.from_config('somewhere', ['/etc/myfile.cfg'])

issues = gl.issues.list()
for issue in issues:
    print(issue.iid, issue.title)
```

## Criando issues

```python
import gitlab

gl = gitlab.Gitlab.from_config('somewhere', ['/etc/myfile.cfg'])

issues = gl.issues.list()

project = gl.projects.get(ID-DO-PROJETO)

project.issues.create(
    {'title': 'I have a bug',
   'description': 'Lorem ipsum...'})

for issue in project.issues.list():
    print(issue.iid, issue.title)
```

## gitlab + click

```python
import click
import gitlab
from decouple import config

'''
Usage: python glab-cli.py --title='Your title' --description='Your description'
'''


gl = gitlab.Gitlab.from_config('somewhere', ['/etc/myfile.cfg'])
project = gl.projects.get(config('GITLAB_PROJECT_ID'))


@click.command()
@click.option('--title', prompt='Title', help='Type the title.')
@click.option('--description', prompt='Description', help='Type the description.')
def create_issue(title, description):
    response = project.issues.create(
        {"title": f"{title}",
         "description": f"{description}"})

    click.echo(response.iid)
    click.echo(response.title)


if __name__ == '__main__':
    create_issue()
```


# 022-criando-issues-por-linha-de-comando-com-bitbucket-cli

## Dica 22 - Criando issues por linha de comando com bitbucket cli

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/N2oYZxixcSU)

## OBSOLETO

### Instalação

`pip install bitbucket-python`

### Usando com Python

> Lembre-se de habilitar a criação de issues no repositório.

```python
from bitbucket.client import Client
from decouple import config

email = config('BITBUCKET_EMAIL')
password = config('BITBUCKET_PASSWORD')
client = Client(email, password)

repository_slug = config('REPOSITORY_SLUG')

repo = client.get_repository(repository_slug)

data = {
    'title': 'Your title',
    'content': {'raw': 'Your description'},
    'kind': 'task'
}
# kind: task or bug

response = client.create_issue(repository_slug, data)
```

### Usando com click

```python
import click
from bitbucket.client import Client
from decouple import config

'''
Usage: python bitbucket_cli.py --title='Your title' --description='Your description' --kind='task'
'''


@click.command()
@click.option('--title', prompt='Title', help='Type the title.')
@click.option('--description', prompt='Description', help='Type the description.')
@click.option('--kind', prompt='Kind', help='Kind is task or bug.')
def create_issue(title, description, kind):
    email = config('BITBUCKET_EMAIL')
    password = config('BITBUCKET_PASSWORD')
    repository_slug = config('REPOSITORY_SLUG')

    client = Client(email, password)

    data = {
        'title': title,
        'content': {'raw': description},
        'kind': kind
    }
    response = client.create_issue(repository_slug, data)

    click.echo(response['title'])


if __name__ == '__main__':
    create_issue()
```


# Dica 23 - Diferença entre JSON dump, dumps, load e loads

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/4AupIlLYkgE)

**Documentação:** [JSON](https://docs.python.org/3/library/json.html)

![json\_loads\_dumps.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-d8fd73e10cb709a9686a709bdeb125ace561de74%2Fjson_loads_dumps.png?alt=media\&token=f8ac21ed-a7b9-4c11-aaa3-da9b76e95e0d)

\--

![json\_load\_dump.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-83ea275a4d11f71cfb209f20f740e6da1006ad41%2Fjson_load_dump.png?alt=media\&token=10fce102-b863-4659-8018-47b6f91e4086)

## [dumps](https://docs.python.org/3/library/json.html#json.dumps)

Serializa um objeto Python para uma string no formato JSON.

`json.dumps(obj)`

```python
import json

my_dict = {
    "name": "Elliot",
    "age": 25
}
json.dumps(my_dict)
```

## [dump](https://docs.python.org/3/library/json.html#json.dump)

Serializa um objeto Python para um arquivo no formato JSON.

`json.dump(obj, fp)`

onde *fp* significa *file-like object*.

```python
import json

my_dict = {
    "name": "Elliot",
    "age": 25
}
with open('/tmp/file.txt', 'w') as f:
    json.dump(my_dict, f)
```

## [loads](https://docs.python.org/3/library/json.html#json.loads)

Deserializa uma string no formato JSON para um arquivo.

`json.loads(s)`

```python
import json

text = """
{
    "name": "Darlene",
    "age": 27
}
"""
json.loads(text)
```

## [load](https://docs.python.org/3/library/json.html#json.load)

Deserializa um arquivo no formato JSON para um arquivo.

`json.load(fp)`

```python
import json

text = """
{
    "name": "Darlene",
    "age": 27
}
"""
with open('/tmp/file.txt', 'r') as f:
    data = json.load(f)

print(data)
```

**Exemplo**

```python
# json_example.py
import json
from io import StringIO
from pprint import pprint


def json_to_string_with_dumps(my_dict):
    '''
    Serializa (encode) objeto para string no formato JSON.
    '''
    return json.dumps(my_dict, indent=4)


def json_to_string_with_dump_stringio(my_dict):
    '''
    Serializa (encode) objeto para string no formato JSON usando StringIO.
    '''
    io = StringIO()
    json.dump(my_dict, io, indent=4)
    return io.getvalue()


def json_to_file_with_dump_open_file(filename, my_dict):
    '''
    Serializa (encode) objeto para arquivo no formato JSON usando open.
    '''
    with open(filename, 'w') as f:
        json.dump(my_dict, f, indent=4)


def string_to_json_with_loads(text):
    '''
    Deserializa (decode) string no formato JSON para objeto.
    '''
    return json.loads(text)


def string_to_json_with_load_stringio(text):
    '''
    Deserializa (decode) string no formato JSON para objeto usando StringIO.
    '''
    io = StringIO(text)
    return json.load(io)


def file_to_json_with_load_open_file(filename):
    '''
    Deserializa (decode) string no formato JSON para arquivo usando open.
    '''
    with open(filename, 'r') as f:
        data = json.load(f)
    return data


if __name__ == '__main__':
    # Serialize (encode)

    my_dict = {
        "name": "Elliot",
        "age": 25
    }
    print(json_to_string_with_dumps(my_dict))
    print(type(json_to_string_with_dumps(my_dict)))

    my_dict = {
        "name": "Elliot",
        "full_name": {"first_name": "Elliot", "last_name": "Alderson"},
        "items": [1, 2.5, "a"],
        "pi": 3.14,
        "active": True,
        "nulo": None
    }
    print(json_to_string_with_dump_stringio(my_dict))
    print(type(json_to_string_with_dump_stringio(my_dict)))

    filename = '/tmp/file.txt'
    my_dict = {
        "name": "Elliot",
        "full_name": {"first_name": "Elliot", "last_name": "Alderson"},
        "items": [1, 2.5, "a"],
        "pi": 3.14,
        "active": True,
        "nulo": None
    }
    json_to_file_with_dump_open_file(filename, my_dict)

    # Deserialize (decode)

    text = """
    {
        "name": "Darlene",
        "age": 27
    }
    """
    pprint(string_to_json_with_load_stringio(text))
    print(type(string_to_json_with_load_stringio(text)))

    pprint(string_to_json_with_loads(text))
    print(type(string_to_json_with_loads(text)))

    pprint(file_to_json_with_load_open_file(filename))
```

[JsonResponse](https://docs.djangoproject.com/en/2.2/ref/request-response/#jsonresponse-objects) \[[source](https://docs.djangoproject.com/en/2.2/_modules/django/http/response/#JsonResponse)]

```python
# core/views.py
import json

from django.http import JsonResponse


def article_json(request):
    text = '''
    {
        "title": "JSON",
        "subtitle": "Entendento JSON dumps e loads",
        "slug": "entendento-json-dumps-e-loads",
        "value": "42"
    }
    '''
    data = json.loads(text)
    pprint(data)
    print(type(data))
    print(data['value'], 'is', type(data['value']))

    data['title'] = 'Introdução ao JSON'
    data['value'] = int(data['value']) + 1
    data['pi'] = 3.14
    data['active'] = True
    data['nulo'] = None
    return JsonResponse(data)
```

```python
# core/urls.py
...
path('articles/json/', v.article_json, name='article_json'),
...
```

Leia mais em [Working With JSON Data in Python](https://realpython.com/python-json/).


# Dica 24 - Barra de progresso

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/YQlwWn48eTw)

* [progress](https://pypi.org/project/progress/)
* [tqdm](https://tqdm.github.io/)
* [click](https://click.palletsprojects.com/en/7.x/) - [click progressbar](https://click.palletsprojects.com/en/7.x/utils/#showing-progress-bars)
* [progressbar 2](https://progressbar-2.readthedocs.io/en/latest/index.html)
* [clint](https://github.com/kennethreitz-archive/clint)
* [with sys](https://stackoverflow.com/a/3160819)
* [gist rg3915](https://gist.github.com/rg3915/b6368374f74d00d9ea045470718a8ddd)
* [progressbar on Jupyter notebook](https://opensource.com/article/20/12/tqdm-python)

Leia mais em [How to Easily Use a Progress Bar in Python](https://codingdose.info/posts/how-to-use-a-progress-bar-in-python/)

### [progress](https://pypi.org/project/progress/)

`pip install progress`

```python
# example01_progress.py
from time import sleep

from progress.bar import Bar

with Bar('Processing...') as bar:
    for i in range(100):
        sleep(0.02)
        bar.next()
```

```
$ python example01_progress.py
```

### [tqdm](https://tqdm.github.io/)

```
pip install tqdm
```

```python
from time import sleep

# example02_tqdm.py
from tqdm import tqdm

for i in tqdm(range(100)):
    sleep(0.02)
    # Do something
```

```
python example02_tqdm.py
```

### [click](https://click.palletsprojects.com/en/7.x/)

[click progressbar](https://click.palletsprojects.com/en/7.x/utils/#showing-progress-bars)

```
pip install click
```

```python
from time import sleep

# example03_click.py
import click

# Fill character is # by default, you can change it
# for any other char you want, or even change the color.
fill_char = click.style('=', fg='yellow')
with click.progressbar(range(100), label='Loading...', fill_char=fill_char) as bar:
    for i in bar:
        sleep(0.02)
```

```
python example03_click.py
```

### [progressbar 2](https://progressbar-2.readthedocs.io/en/latest/index.html)

```
pip install progressbar2
```

```python
# example04_progressbar2.py
from time import sleep

from progressbar import progressbar

for i in progressbar(range(100)):
    sleep(0.02)
```

```
python example04_progressbar2.py
```

### [clint](https://github.com/kennethreitz-archive/clint)

```
pip install clint
```

```python
# example05_clint.py
from time import sleep

from clint.textui import progress

print('Clint - Regular Progress Bar')
for i in progress.bar(range(100)):
    sleep(0.02)

print('Clint - Mill Progress Bar')
for i in progress.mill(range(100)):
    sleep(0.02)
```

```
python example05_clint.py
```

### [with sys](https://stackoverflow.com/a/3160819)

```python
import sys
# example06_sys.py
import time

toolbar_width = 40

# setup toolbar
sys.stdout.write("[%s]" % (" " * toolbar_width))
sys.stdout.flush()
sys.stdout.write("\b" * (toolbar_width+1)) # return to start of line, after '['

for i in range(toolbar_width):
    time.sleep(0.1) # do real work here
    # update the bar
    sys.stdout.write("-")
    sys.stdout.flush()

sys.stdout.write("]\n") # this ends the progress bar
```

```
python example06_sys.py
```

### [gist rg3915](https://gist.github.com/rg3915/b6368374f74d00d9ea045470718a8ddd)

```python
# example07_sys.py
import sys
import time


def progressbar(it, prefix="", size=60, file=sys.stdout):
    count = len(it)

    def show(j):
        x = int(size * j / count)
        file.write("%s[%s%s] %i/%i\r" %
                   (prefix, "#" * x, "." * (size - x), j, count))
        file.flush()
    show(0)
    for i, item in enumerate(it):
        yield item
        show(i + 1)
    file.write("\n")
    file.flush()


users = ['Regis', 'Abel', 'Eduardo', 'Elaine']


for user in progressbar(users, "Processing: "):
    time.sleep(0.1)
    # Do something.


for i in progressbar(range(42), "Processing: "):
    time.sleep(0.05)
    # Do something.
```

```
python example07_sys.py
```

### [progressbar on Jupyter notebook](https://opensource.com/article/20/12/tqdm-python)

```
$ jupyter notebook
```

```python
# progressbar_jupyter.ipynb
import sys

if hasattr(sys.modules["__main__"], "get_ipython"):
    from tqdm import notebook as tqdm
else:
    import tqdm

from time import sleep

n = 0
for i in tqdm.trange(100):
    n += 1
    sleep(0.01)

url = "https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz"
import httpx

with httpx.stream("GET", url) as response:
    total = int(response.headers["Content-Length"])
    with tqdm.tqdm(total=total) as progress:
        for chunk in response.iter_bytes():
            progress.update(len(chunk))
```


# Dica 25 - Rodando Shell script dentro do Python

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/r3MIUX2QTEI)

Para rodar Shell script dentro do Python só precisamos do [subprocess](https://docs.python.org/3/library/subprocess.html).

```python
# subprocess01.py
import subprocess
from datetime import datetime

subprocess.call('echo "Hello"', shell=True)

subprocess.run('echo "Running"', shell=True)

now = datetime.now()

subprocess.run(f'notify-send --urgency=LOW "{now}"', shell=True)


def write_numbers(n):
    return ' '.join([str(i) for i in range(n)])


# print(write_numbers(5))

subprocess.run(f'echo {write_numbers(10)} > /tmp/numbers.txt', shell=True)
subprocess.run('cat /tmp/numbers.txt', shell=True)

subprocess.run('wc -l /tmp/out.log', shell=True)
```

```
$ python subprocess01.py
```


# Dica 26 - Rodando Python dentro do Shell script

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/WjvVTqfUNMI)

Leia mais em:

[Grande Portal - Shell script 1](http://grandeportal.github.io/shell/2016/shell-script1/)

[Grande Portal - Shell script 2](http://grandeportal.github.io/shell/2016/shell-script2/)

[Grande Portal - Shell script 3](http://grandeportal.github.io/shell/2016/shell-script3/)

Assista também:

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://www.youtube.com/watch?v=NoQW5CGAGNA)

[Mini-curso Shell script 1](https://www.youtube.com/watch?v=NoQW5CGAGNA)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://www.youtube.com/watch?v=aspwrDLSrPI)

[Mini-curso Shell script 2](https://www.youtube.com/watch?v=aspwrDLSrPI)

**Exemplo 1:**

```
# running_python01.sh
python -c "print('Rodando Python dentro do Shell script')"
```

```
$ source running_python01.sh
```

Ou

```
$ chmod +x running_python01.sh
$ ./running_python01.sh
```

**Exemplo 2:**

```
# ./running_python02.sh 1 2
# ./running_python02.sh 2 1
# ./running_python02.sh 2 2

a=${1}
b=${2}

if [[ $a -eq $b ]]; then
    python -c "print('${a} é igual a ${b}')"
elif [[ $a -lt $b ]]; then
    python -c "print('${a} é menor que ${b}')"
else
    python -c "print('${a} é maior que ${b}')"
fi
```

```
chmod +x running_python02.sh
./running_python02.sh 1 2
./running_python02.sh 2 1
./running_python02.sh 2 2
```

**Exemplo 3:**

```
# ./running_python03.sh 1 10
# ./running_python03.sh 35 42

start_value=${1}
end_value=${2}

function join { local IFS="$1"; shift; echo "$*"; }

if [[ $start_value -gt $end_value ]]; then
    python -c "print('O valor inicial não pode ser maior que o valor final.')"
else
    IDS=$(seq -s ' ' $start_value $end_value)

    for id in $IDS; do
        python -c "print('$id')"
    done

    python -c "print('$IDS')"
    python -c "print('$IDS'.split())"
    python -c "print([int(i) for i in '$IDS'.split()])"
    python -c "print(sum([int(i) for i in '$IDS'.split()]))"
    python -c "ids=[int(i) for i in '$IDS'.split()]; print(ids)"
    # Não dá pra usar o laço for do Python na mesma linha, então façamos
    echo "IDS:" $IDS
    result=$(join , ${IDS[@]})
    echo "result:" $result
    python running_python03.py -ids $result
fi
```

```python
# running_python03.py
import click


@click.command()
@click.option('-ids', prompt='Ids', help='Digite uma sequência de números separado por vírgula.')
def get_numbers(ids):
    print('>>>', ids)
    for id in ids.split(','):
        print(id)


if __name__ == '__main__':
    get_numbers()
```

```
chmod +x running_python03.sh
./running_python03.sh 1 10
./running_python03.sh 35 42
```

**Exemplo 4:** Não está no video.

```
# running_python04.sh
# Como pegar o resultado do Python e usar numa variável no Shell script.

result=$(python -c "result = 42; print(result)" | xargs echo $var1)
echo 'Resultado:' $result
echo 'Dobro:' $(( $result*2 ))

result2=$(python -c "result = sum([i for i in range(11)]); print(result)" | xargs echo $var2)
echo 'Resultado:' $result2
echo 'Dobro:' $(( $result2*2 ))


# Como usar comandos multilinha.

result=$(python << EOF
aux = []
for i in range(1, 11):
    aux.append(i)
print(sum(aux))
EOF
)
echo 'Resultado:' $result

python << EOF
aux = []
for i in range(1, 11):
    print(i)
    aux.append(i)
print(f'Total: {sum(aux)}')
EOF

result=$(python fibonacci.py | xargs echo $f)
echo 'Fibonacci'
echo $result
```

```python
# fibonacci.py
# Function for nth Fibonacci number

def Fibonacci(n):
    if n < 0:
        print("Incorrect input")
    # First Fibonacci number is 0
    elif n == 0:
        return 0
    # Second Fibonacci number is 1
    elif n == 1:
        return 1
    else:
        return Fibonacci(n - 1) + Fibonacci(n - 2)


print(Fibonacci(9))
# This code is contributed by Saket Modi
```

<https://www.geeksforgeeks.org/program-for-nth-fibonacci-number/>


# Dica 27 - Retornando os nomes dos campos do model

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/lU2J5ZCJiyE)

```python
$ python manage.py shell_plus

>>> [field.name for field in User._meta.get_fields()]
['logentry',
 'id',
 'password',
 'last_login',
 'is_superuser',
 'username',
 'first_name',
 'last_name',
 'email',
 'is_staff',
 'is_active',
 'date_joined',
 'groups',
 'user_permissions']

>>> [field.name for field in Article._meta.get_fields()]
['id', 'title', 'subtitle', 'slug', 'category', 'published_date']
```


# Dica 28 - Admin: Usando short description

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/Uwwr77SR8EE)

Quando não conseguimos usar o `dunder` no `list_display` do admin, então usamos o `short_description`.

```python
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'slug', 'get_published_date', 'get_category')
    ...

    def get_published_date(self, obj):
        if obj.published_date:
            return obj.published_date.strftime('%d/%m/%Y')

    get_published_date.short_description = 'Data de Publicação'

    def get_category(self, obj):
        if obj.category:
            return obj.category.title

    get_category.short_description = 'Categoria'
```


# Dica 29 - Django Admin: Criando actions no Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/WofnItMvqKU)

<https://docs.djangoproject.com/en/3.1/ref/contrib/admin/actions/>

Em `models.py` considere

```python
# models.py
STATUS_CHOICES = (
    ('d', 'Rascunho'),
    ('p', 'Publicado'),
    ('w', 'Retirado'),
)


class Article(models.Model):
    ...
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)
```

E em `admin.py`

```python
# admin.py
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    ...
    actions = ('make_published',)

    def make_published(self, request, queryset):
        count = queryset.update(status='p')

        if count == 1:
            msg = '{} artigo foi publicado.'
        else:
            msg = '{} artigos foram publicados.'

        self.message_user(request, msg.format(count))

    make_published.short_description = "Publicar artigos"
```


# Dica 30 - Django Admin: Editando direto na listview do Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/3skHZrRR1PE)

![img/editar\_admin.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-e035a3b1c725c40bbc306089428601feddbe73c6%2Feditar_admin.png?alt=media\&token=2f1a7830-1531-4c3f-a89f-8354e49acc9c)

```python
# admin.py
...
list_editable = ('title', 'status')
...
```


# Dica 31 - Django Admin: Pegando usuário logado no Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/qP5-ZICzHyM)

Em `models.py` considere

```python
# models.py
from django.contrib.auth.models import User


class Article(models.Model):
    ...
    user = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True
    )
```

E em `admin.py`

```python
# admin.py
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    ...

    def save_model(self, request, obj, form, change):
        if not change:
            obj.user = request.user
            obj.save()
        super(ArticleAdmin, self).save_model(request, obj, form, change)
```


# Dica 32 - Django Admin: Sobreescrevendo os templates do Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/0d9VcL8ssTg)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

![img/botao\_admin.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-2ec6c6d9d28db2c9aa0d1aeda41ec13927d4fb87%2Fbotao_admin.png?alt=media)

Se você olhar em

<https://github.com/django/django/tree/main/django/contrib/admin/templates/admin>

verá todos os templates usados no Admin.

Na pasta da virtualenv do seu projeto também.

```
ls -l .venv/lib/python3.8/site-packages/django/contrib/admin/templates/admin/
cat .venv/lib/python3.8/site-packages/django/contrib/admin/templates/admin/change_list.html
```

Olhando na doc do Django em [Set up your projects admin template directories](https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#set-up-your-projects-admin-template-directories) nós vemos que devemos ter a seguinte estrutura de pastas:

```
myproject
├── core
│   ├── templates
│   │   ├── admin
│   │   │   ├── base_site.html
│   │   │   ├── login.html
│   │   │   ├── core
│   │   │   │   ├── article
│   │   │   │   │   └── change_list.html
│   │   │   │   └── change_list.html
```

Então vamos criar nossas pastas

```
mkdir -p myproject/core/templates/admin/core/article
```

Agora vamos criar o primeiro `change_list.html`

```
touch myproject/core/templates/admin/core/change_list.html
```

E seu conteúdo será:

```html
{\% extends "admin/change_list.html" %}

{\% block object-tools-items %}
  {{ block.super }}
  <li>
    <a href="botao-da-app/">
      Novo botão
    </a>
  </li>
{\% endblock %}
```

Depois

```
touch myproject/core/templates/admin/core/article/change_list.html
```

Com o conteúdo:

```html
{\% extends "admin/change_list.html" %}

{\% block object-tools-items %}
  {{ block.super }}
  <li>
    <a href="botao-artigo/">
      Botão do Artigo
    </a>
  </li>
{\% endblock %}
```

Para que o Django Admin reconheça esses templates precisamos configurar o `settings.py`

```python
# settings.py
TEMPLATES = [
    {
        ...
        'DIRS': [
            BASE_DIR,
            os.path.join(BASE_DIR, 'templates')
        ],
        ...
    },
]
```

Agora edite `admin.py`

```python
from django.contrib import admin, messages
# admin.py
from django.shortcuts import redirect
from django.urls import path


@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    ...

    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path(
                'botao-artigo/',
                self.admin_site.admin_view(self.minha_funcao, cacheable=True)
            ),
        ]
        return my_urls + urls

    def minha_funcao(self, request):
        print('Ao clicar no botão, faz alguma coisa...')
        messages.add_message(
            request,
            messages.INFO,
            'Ação realizada com sucesso.'
        )
        return redirect('admin:core_article_changelist')


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    ...

    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path(
                'botao-da-app/',
                self.admin_site.admin_view(self.minha_funcao_category, cacheable=True)
            ),
        ]
        return my_urls + urls

    def minha_funcao_category(self, request):
        print('Ao clicar no botão, faz alguma coisa em category...')
        messages.add_message(
            request,
            messages.INFO,
            'Ação realizada com sucesso.'
        )
        return redirect('admin:core_category_changelist')
```

## Sobreescrevendo a tela de login do Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/ci4LtLxDCRM)

![img/login.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-945668e31e47b3d12f9dabf41320308c327bcb7e%2Flogin.png?alt=media)

Em [AdminSite attributes](https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#adminsite-attributes) nós temos o atributo [AdminSite.login\_template](https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#django.contrib.admin.AdminSite.login_template).

A partir daí podemos fazer

```python
# admin.py
admin.site.login_template = 'myproject/core/templates/admin/login.html'
```

Vendo

```
cat .venv/lib/python3.8/site-packages/django/contrib/admin/templates/admin/login.html
cat .venv/lib/python3.8/site-packages/django/contrib/admin/templates/admin/base_site.html
```

E nos templates

```
touch myproject/core/templates/admin/login.html
```

```html
<!-- myproject/core/templates/admin/login.html -->
{\% extends "admin/login.html" %}
{\% load static %}

{\% block branding %}
  <h1 id="site-name">
    <a href="{\% url 'admin:index' %}">
      <img src="{\% static 'img/django-logo-negative.png' %}" alt="django-logo-negative.png" width="100px">
    </a>
  </h1>
{\% endblock %}

{\% block extrastyle %}
  {{ block.super }}
  <link rel="stylesheet" type="text/css" href="{\% static "css/login.css" %}" />
  {{ form.media }}
{\% endblock %}
```

E pra caprichar no CSS

```css
/* myproject/core/static/css/login.css */
body.login {
    background: url("../img/headset.jpg") no-repeat center center;
    background-size: 100% auto;
}

html {
    min-height: 100%;
}
```

<https://www.djangoadmintutorials.com/how-to-customize-django-admin-login-page/>

## Inserindo um logo no header do Admin

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/7NcghC_eySs)

![img/header\_admin.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-8a67403c8ef352f27ca2a492cbc741c590dbb67b%2Fheader_admin.png?alt=media)

Basta criar `base_site.html`

```
touch myproject/core/templates/admin/base_site.html
```

```html
{\% extends "admin/base_site.html" %}
{\% load static %}

{\% block branding %}
  <h1 id="site-name">
    <a href="{\% url 'admin:index' %}">
      <img src="{\% static 'img/django-logo-negative.png' %}" alt="django-logo-negative.png" width="70px">
    </a>
  </h1>
{\% endblock %}
```

**Importante:** mude a ordem das `apps` em `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    'myproject.core',
    'django.contrib.admin',
    ...
]
```

<https://books.agiliq.com/projects/django-admin-cookbook/en/latest/logo.html>


# Dica 33 - Github cli

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/Y5VO3u2hp10)

<https://cli.github.com/>

```
gh auth login
gh repo clone rg3915/dicas-de-django
gh repo view
gh pr checks
gh pr create
gh pr status
gh pr merge
gh issue list
```

```
gh issue create --title "Github cli" --body "Experimentar Github cli https://cli.github.com" \
--label "enhancement" \
--assignee "@me"
```

```
gh issue create -t "Github cli" -b "Experimentar Github cli
https://cli.github.com" \
-l "enhancement" \
-a "@me"
```

Fechar issue

```
git commit -m 'Usando o github cli. close #34'
git push
```


# Dica 34 - Django: custom template tags

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/ldMf8AW2h4Y)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

<https://docs.djangoproject.com/en/3.2/ref/templates/builtins/>

### Built-in tags

```html
{\% for obj in object_list %}
  <tr>
    <td>{{ forloop.counter }}</td>
    ...
    {\% if obj.category.title == 'Django' %}
      <td>{{ obj.category }}</td>
    {\% endif %}
  </tr>
{\% endfor %}
```

### Built-in filter

```html
...
<td>{{ forloop.counter }}</td>
<td>{{ obj.title|slugify }}</td>
<td>{{ obj.title|truncatechars:13 }}</td>
<td>{{ obj.subtitle|safe|default:"---" }}</td>
<td>{{ obj.published_date|date:"d/m/Y" }}</td>
...
```

```
subtitle='<p>lorem</p>'
```

```html
{{ obj.subtitle|safe }}
```

### Writing custom template filters

#### Code layout

<https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/#code-layout>

```
core
├── __init__.py
├── models.py
├── templatetags
│   ├── __init__.py
│   ├── model_name_tags.py
│   └── usergroup_tags.py
```

<https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/#writing-custom-template-filters>

```
mkdir myproject/core/templatetags
touch myproject/core/templatetags/__init__.py
touch myproject/core/templatetags/usergroup_tags.py
```

```python
# usergroup_tags.py
from django import template

register = template.Library()


@register.filter('name_group')
def name_group(user):
    ''' Retorna o nome do grupo do usuário. '''
    _groups = user.groups.first()
    if _groups:
        return _groups.name
    return ''


@register.filter('has_group')
def has_group(user, group_name):
    ''' Verifica se este usuário pertence a um grupo. '''
    if user:
        groups = user.groups.all().values_list('name', flat=True)
        return True if group_name in groups else False
    return False
```

```html
{\% load usergroup_tags %}

{\% if request.user|has_group:"Autor" %}
É Autor.
{\% endif %}
```

### Writing custom template tags

<https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/#writing-custom-template-tags>

```
touch myproject/core/templatetags/model_name_tags.py
```

```python
# model_name_tags.py
from django import template

register = template.Library()


@register.simple_tag
def model_name(value):
    '''
    Django template filter which returns the verbose name of a model.
    '''
    if hasattr(value, 'model'):
        value = value.model

    return value._meta.verbose_name.title()


@register.simple_tag
def model_name_plural(value):
    '''
    Django template filter which returns the plural verbose name of a model.
    '''
    if hasattr(value, 'model'):
        value = value.model

    return value._meta.verbose_name_plural.title()
```

```html
{\% load model_name_tags %}

Lista de {\% model_name_plural model %}
```


# Dica 35 - Django: passando usuário logado no formulário

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/69jPO_v6ldI)

```python
# forms.py
from django import forms

from .models import Person


class PersonForm(forms.ModelForm):

    class Meta:
        model = Person
        fields = '__all__'

    def __init__(self, user=None, *args, **kwargs):
        super(PersonForm, self).__init__(*args, **kwargs)
        # my_field = MyModel.objects.filter(user=user)
        if user.is_authenticated:
            print(user)
        else:
            print('Não')
```

```python
# views.py
def person_create(request):
    template_name = 'core/person_form.html'
    # Não esquecer do request.user como primeiro parâmetro.
    form = PersonForm(request.user, request.POST or None)

    if request.method == 'POST':
        if form.is_valid():
            form.save()
            return redirect('person:person_list')

    context = {'form': form}
    return render(request, template_name, context)
```

```python
# models.py
class Person(UuidModel):
    first_name = models.CharField('nome', max_length=50)
    last_name = models.CharField('sobrenome', max_length=50, null=True, blank=True)  # noqa E501
    email = models.EmailField(null=True, blank=True)

    class Meta:
        ordering = ('first_name',)
        verbose_name = 'pessoa'
        verbose_name_plural = 'pessoas'

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name
```


# Dica 36 - Django: visualizando seus modelos com graph models

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/99dOVsDBUxg)

```
sudo apt-get install -y graphviz libgraphviz-dev pkg-config
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install pygraphviz
pip uninstall pyparsing
pip install -Iv https://pypi.python.org/packages/source/p/pyparsing/pyparsing-1.5.7.tar.gz#md5=9be0fcdcc595199c646ab317c1d9a709
pip install pydot
pip install django-extensions
```

E em `INSTALLED_APPS`

```
INSTALLED_APPS = [
    ...
    'django_extensions',
    ...
]
```

Depois rode

```
python manage.py graph_models -e -g -l dot -o core.png core # only app core
python manage.py graph_models -a -g -o models.png # all
```

### core

![core.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-5a7163a29155fc140c53bd20a2ecb28e814964dc%2Fcore.png?alt=media\&token=622fb09d-596c-44f1-a82e-3ec5c6bb809f)

### models

![models.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-f5a24954e1996cc5aa8429af01ff1d447a777f67%2Fmodels.png?alt=media\&token=59c4ce69-44a4-4269-b8cd-50dd4ed79082)


# Dica 37 - Faker

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/ubgVHtLhubw)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

<https://faker.readthedocs.io/en/master/>

O Faker é uma biblioteca ideal para popular o seu banco de dados com dados aleatórios.

Vamos trabalhar em cima do model `Person`, e acrescentar mais uns campos no modelo.

```python
# models.py
class Person(UuidModel):
    first_name = models.CharField('nome', max_length=50)
    last_name = models.CharField('sobrenome', max_length=50, null=True, blank=True)  # noqa E501
    email = models.EmailField(null=True, blank=True)
    bio = models.TextField('biografia', null=True, blank=True)
    birthday = models.DateField('nascimento', null=True, blank=True)
```

Instale o Faker

```
pip install faker
```

E vamos criar um novo comando em `management`

```
touch myproject/core/management/commands/create_data.py
```

```python
# create_data.py
from django.core.management.base import BaseCommand
from django.utils.text import slugify
from faker import Faker

from myproject.core.models import Person
from myproject.utils.progress_bar import progressbar

fake = Faker()


def gen_email(first_name: str, last_name: str):
    first_name = slugify(first_name)
    last_name = slugify(last_name)
    email = f'{first_name}.{last_name}@email.com'
    return email


def get_person():
    first_name = fake.first_name()
    last_name = fake.first_name()
    email = gen_email(first_name, last_name)
    bio = fake.paragraph(nb_sentences=5)
    birthday = fake.date()
    data = dict(
        first_name=first_name,
        last_name=last_name,
        email=email,
        bio=bio,
        birthday=birthday,
    )
    return data


def create_persons():
    aux_list = []
    for _ in progressbar(range(100), 'Persons'):
        data = get_person()
        obj = Person(**data)
        aux_list.append(obj)
    Person.objects.bulk_create(aux_list)


class Command(BaseCommand):
    help = 'Create data.'

    def handle(self, *args, **options):
        create_persons()
```

Editar `admin.py`

```
admin.site.register(Person)
```

Criar `progress_bar.py`

```
mkdir myproject/utils
touch myproject/utils/progress_bar.py
```

```python
# progress_bar.py
import sys


def progressbar(it, prefix="", size=60, file=sys.stdout):
    count = len(it)

    def show(j):
        x = int(size * j / count)
        file.write("%s[%s%s] %i/%i\r" %
                   (prefix, "#" * x, "." * (size - x), j, count))
        file.flush()
    show(0)
    for i, item in enumerate(it):
        yield item
        show(i + 1)
    file.write("\n")
    file.flush()
```

Editar `views.py`

```python
# views.py
from django.views.generic import ListView


class PersonListView(ListView):
    model = Person
    template_name = 'core/person_list.html'
```

Editar `urls.py`

```python
# urls.py
...
path('persons/', v.PersonListView.as_view(), name='person_list'),
```

Editar `person_list.html`

```html
# person_list.html
{\% extends "base.html" %}

{\% block content %}
  <h1>Lista de pessoas</h1>

  <div class="row">
    <div class="col">
      <form action="." method="GET">
        <div class="row">
          <div class="col">
            <input name="search" class="form-control mb-2" type="text" placeholder="Buscar..." />
          </div>
          <div class="col-auto">
            <button class="btn btn-success mb-2" type="submit">OK</button>
            <button class="btn btn-link mb-2">Limpar</button>
          </div>
        </div>
      </form>
    </div>
  </div>

  <table class="table">
    <thead>
      <tr>
        <th>Nome</th>
        <th>Sobrenome</th>
        <th>E-mail</th>
        <th>Biografia</th>
        <th>Nascimento</th>
      </tr>
    </thead>
    <tbody>
      {\% for object in object_list %}
        <tr>
          <td>{{ object.first_name }}</td>
          <td>{{ object.last_name }}</td>
          <td>{{ object.email }}</td>
          <td>{{ object.bio }}</td>
          <td>{{ object.birthday|date:"d/m/Y" }}</td>
        </tr>
      {\% endfor %}
    </tbody>
  </table>
{\% endblock content %}
```


# Dica 38 - Django: Paginação + Filtros

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/eXipSfa-HOQ)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Primeiro vamos definir a paginação:

Edite `views.py`

```python
# views.py
class PersonListView(ListView):
    model = Person
    template_name = 'core/person_list.html'
    paginate_by = 5
```

Edite `person_list.html`

```html
<!-- person_list.html -->
...
{\% include "includes/pagination.html" %}
```

Criar `includes/pagination.html`

```
mkdir myproject/core/templates/includes
touch myproject/core/templates/includes/pagination.html
```

```html
<!-- pagination.html -->
<!-- https://gist.github.com/rg3915/01ca76f099f431c24bc0536bef83076b -->
<!-- Use https://gist.github.com/rg3915/01ca76f099f431c24bc0536bef83076b#file-pagination02-html -->
<div class="row text-center">
  <div class="col-lg-12">
    <ul class="pagination">
      {\% if page_obj.has_previous %}
        <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li>
      {\% endif %}

      {\% for pg in page_obj.paginator.page_range %}
        <!-- Sempre mostra as 3 primeiras e 3 últimas páginas -->
          {\% if pg == 1 or pg == 2 or pg == 3 or pg == page_obj.paginator.num_pages or pg == page_obj.paginator.num_pages|add:'-1' or pg == page_obj.paginator.num_pages|add:'-2' %}
            {\% if page_obj.number == pg %}
              <li class="page-item active"><a class="page-link" href="?page={{ pg }}">{{ pg }}</a></li>
            {\% else %}
              <li class="page-item"><a class="page-link" href="?page={{ pg }}">{{ pg }}</a></li>
            {\% endif %}

          {\% else %}

            {\% if page_obj.number == pg %}
              <li class="page-item active"><a class="page-link" href="?page={{ pg }}">{{ pg }}</a></li>
            {\% elif pg > page_obj.number|add:'-4' and pg < page_obj.number|add:'4' %} <!-- Mostra 3 páginas antes e 3 páginas depois da atual -->
              <li class="page-item"><a class="page-link" href="?page={{ pg }}">{{ pg }}</a></li>
            {\% elif pg == page_obj.number|add:'-4' or pg == page_obj.number|add:'4' %}
              <li class="page-item"><a class="page-link" href="">...</a></li>
            {\% endif %}
          {\% endif %}
        {\% endfor %}

        {\% if page_obj.has_next %}
          <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">&raquo;</a></li>
        {\% endif %}
    </ul>
  </div>
</div>
```

Editar novamente `views.py`

```python
# views.py
from django.db.models import Q


class PersonListView(ListView):
    model = Person
    template_name = 'core/person_list.html'
    paginate_by = 5

    def get_queryset(self):
        queryset = super(PersonListView, self).get_queryset()

        data = self.request.GET
        search = data.get('search')

        if search:
            queryset = queryset.filter(
                Q(first_name__icontains=search) |
                Q(last_name__icontains=search) |
                Q(email__icontains=search) |
                Q(bio__icontains=search)
            )

        return queryset
```

Criar templatetags `url_replace.py`

```
touch myproject/core/templatetags/url_replace.py
```

```python
# url_replace.py
# https://stackoverflow.com/a/62587351/802542
from django import template

register = template.Library()


@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()
    query.pop('page', None)
    query.update(kwargs)
    return query.urlencode()
```

E finalmente edite `pagination.html` trocando todos os `href`

```html
<!-- pagination.html -->
{\% load url_replace %}
...
href="?{\% url_replace page=page_obj.previous_page_number %}"
...
href="?{\% url_replace page=pg %}"
...
href="?{\% url_replace page=page_obj.next_page_number %}"
```


# Dica 39 - Django Admin: display decorator (Django 3.2+)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/lKEPuwBjHss)

<https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#the-display-decorator>

**IMPORTANTE:** Usei um [boilerplate](https://github.com/rg3915/dicas-de-django#11---django-boilerplate) para mostrar essa feature num projeto separado. Assista o video no YouTube.

O Django 3.2+ tem uma feature chamada `display` decorator.

Antigamente você fazia `short_description` assim:

```python
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'get_published_date')

    def get_published_date(self, obj):
        if obj.published_date:
            return obj.published_date.strftime('%d/%m/%Y')

    get_published_date.short_description = 'Data de Publicação'
```

... e ainda funciona.

Mas a partir do Django 3.2+ você pode usar o `display` decorator.

```python
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'get_published_date', 'is_published')

    # Django 3.2+
    @admin.display(description='Data de Publicação')
    def get_published_date(self, obj):
        if obj.published_date:
            return obj.published_date.strftime('%d/%m/%Y')

    @admin.display(boolean=True, ordering='-published_date', description='Publicado')
    def is_published(self, obj):
        return obj.published_date is not None
```


# Dica 40 - Formulários: date, datetime, duration e templatetags de data

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/RxECiVYUh5U)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Vamos falar sobre como trabalhar com date, datetime, duration. Para isso vamos usar os seguintes links como referência:

<https://docs.djangoproject.com/en/3.2/ref/forms/fields/>

<https://docs.djangoproject.com/en/3.2/ref/utils/#django.utils.dateparse.parse\\_duration>

<https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#date>

<https://github.com/igorescobar/jQuery-Mask-Plugin>

<https://igorescobar.github.io/jQuery-Mask-Plugin/docs.html>

Lib JS via CDN.

`<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.min.js"></script>`

Mas antes vamos criar uma nova app chamada `travel`.

Entre na pasta `myproject` e crie a nova app.

```
cd myproject
python ../manage.py startapp travel
```

Configure em `INSTALLED_APPS`.

```python
INSTALLED_APPS = [
    ...
    'myproject.travel',
]
```

Em `models.py` crie uma classe `Travel`.

```python
# travel/models.py
cat << EOF > travel/models.py
from django.db import models


class Travel(models.Model):
    destination = models.CharField('destino', max_length=200)
    date_travel = models.DateField('data', null=True, blank=True)
    datetime_travel = models.DateTimeField('data/hora', null=True, blank=True)
    time_travel = models.TimeField('tempo', null=True, blank=True)
    duration_travel = models.DurationField('duração', null=True, blank=True)

    class Meta:
        ordering = ('destination',)
        verbose_name = 'viagem'
        verbose_name_plural = 'viagens'

    def __str__(self):
        return self.destination

EOF
```

Em seguida rode o comando

```
cd ..
python manage.py makemigrations
python manage.py migrate
```

Agora vamos editar o `urls.py` principal.

```python
# urls.py
urlpatterns = [
    ...
    path('travel/', include('myproject.travel.urls', namespace='travel')),
    ...
]

```

Edite `travel/urls.py`.

```python
# travel/urls.py
cat << EOF > myproject/travel/urls.py
from django.urls import path

from myproject.travel import views as v

app_name = 'travel'


urlpatterns = [
    path('', v.TravelListView.as_view(), name='travel_list'),
    path('create/', v.TravelCreateView.as_view(), name='travel_create'),
]

EOF
```

Edite `travel/views.py`.

```python
# travel/views.py
cat << EOF > myproject/travel/views.py
from django.urls import reverse_lazy
from django.views.generic import CreateView, ListView

from .forms import TravelForm
from .models import Travel


class TravelListView(ListView):
    model = Travel
    paginate_by = 10


class TravelCreateView(CreateView):
    model = Travel
    form_class = TravelForm
    success_url = reverse_lazy('travel:travel_list')

EOF
```

Crie a pasta

```
mkdir -p myproject/travel/templates/travel
```

Edite `travel/templates/travel/travel_list.html`

```html
cat << EOF > myproject/travel/templates/travel/travel_list.html
<!-- travel_list.html -->
{\% extends "base.html" %}

{\% block content %}
  <h1>
    Viagens
    <a class="btn btn-primary" href="{\% url 'travel:travel_create' %}">Adicionar</a>
  </h1>
  <table class="table">
    <thead>
      <tr>
        <th>Destino</th>
        <th>Data</th>
        <th>Data e Hora</th>
        <th>Time</th>
        <th>Duração</th>
      </tr>
    </thead>
    <tbody>
      {\% for object in object_list %}
        <!-- https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#date -->
        <tr>
          <td>{{ object.destination }}</td>
          <td>{{ object.date_travel|default:'---' }}</td>
          <td>{{ object.datetime_travel|default:'---' }}</td>
          <td>{{ object.time_travel|default:'---' }}</td>
          <td>{{ object.duration_travel|default:'---' }}</td>
        </tr>
      {\% endfor %}
    </tbody>
  </table>
  {\% include "includes/pagination.html" %}
{\% endblock content %}
EOF
```

Edite `travel/templates/travel/travel_form.html`

```html
cat << EOF > myproject/travel/templates/travel/travel_form.html
<!-- travel_form.html -->
{\% extends "base.html" %}

{\% block content %}
  <h1>Formulário</h1>
  <div class="cols-6">
    <form class="form-horizontal" action="." method="POST">
      <div class="col-sm-6">
        {\% csrf_token %}
        {{ form.as_p }}
        <div class="form-group">
          <button type="submit" class="btn btn-primary">Salvar</button>
        </div>
      </div>
    </form>
  </div>
{\% endblock content %}
EOF
```

Edite `travel/forms.py`

```python
cat << EOF > myproject/travel/forms.py
from django import forms

from .models import Travel


class TravelForm(forms.ModelForm):
    required_css_class = 'required'

    class Meta:
        model = Travel
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(TravelForm, self).__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
EOF
```

Edite `myproject/core/templates/nav.html`

```html
<li class="nav-item">
    <a class="nav-link" href="{\% url 'travel:travel_list' %}">Viagens</a>
</li>
```

Rode a aplicação e cadastre uma viagem com os seguintes dados:

```
Destino: Japão
Data: 2021-07-18
Data/hora: 2021-07-18 01:59
Tempo: 23:01:58
Duração: 26:01:02
```

Edite `travel/admin.py`

```python
cat << EOF > myproject/travel/admin.py
from django.contrib import admin

from .models import Travel


@admin.register(Travel)
class TravelAdmin(admin.ModelAdmin):
    list_display = (
        'destination',
        'date_travel',
        'datetime_travel',
        'time_travel',
        'duration_travel',
    )
    search_fields = ('destination',)

EOF
```

E abra o Admin.

Edite `travel/templates/travel/travel_list.html` novamente

Vejamos os templatetags.

```html
...
<td>{{ object.date_travel|date:"d/m/Y"|default:'---' }}</td>
<td>{{ object.datetime_travel|date:"d/m/Y H:i:s"|default:'---' }}</td>
<td>{{ object.time_travel|time:"H:i:s"|default:'---' }}</td>
...
```

Edite `travel/forms.py` novamente

```python
# travel/forms.py
...
class TravelForm(forms.ModelForm):

    date_travel = forms.DateField(
        label='Data',
        widget=forms.DateInput(
            format='%Y-%m-%d',
            attrs={
                'type': 'date',
            }),
        input_formats=('%Y-%m-%d',),
    )
    datetime_travel = forms.DateTimeField(
        label='Data/Hora',
        widget=forms.DateTimeInput(
            format='%Y-%m-%dT%H:%M',
            attrs={
                'type': 'datetime-local',
            }),
        input_formats=('%Y-%m-%dT%H:%M',),
    )
    time_travel = forms.TimeField(
        label='Tempo',
        widget=forms.TimeInput(
            format='%H:%M',
            attrs={
                'type': 'time',
            }),
        input_formats=('%H:%M',),
    )
...
```

Edite `core/base.html`

```html
{\% block css %}{\% endblock css %}
...
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

{\% block js %}{\% endblock js %}
```

Edite `travel/templates/travel/travel_form.html` novamente

```html
{\% block css %}
  <style>
    p {
      color: #000;
    }
    label.required:after {
      content: "*";
      color: red;
    }
  </style>
{\% endblock css %}
...
{\% block js %}

<!-- https://github.com/igorescobar/jQuery-Mask-Plugin -->
<!-- https://igorescobar.github.io/jQuery-Mask-Plugin/docs.html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.min.js"></script>

<script>
  $('#id_duration_travel').mask('00:00:00');
</script>

{\% endblock js %}
```

Compare o formato do datetime no templatetags e no formulário:

```
date:"d/m/Y H:i:s"       # templatags
format='%Y-%m-%dT%H:%M'  # forms (ISOformat)
```

<https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#date>


# Dica 41 - django-seed

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/9IVp4mLsrdc)

O [django-seed](https://github.com/Brobin/django-seed) é uma lib para que gera dados aleatórios para o seu modelo de uma forma rápida e simples.

```
pip install django-seed
```

Adicione em `INSTALLED_APPS` em `settings.py`:

```python
INSTALLED_APPS = [
    ...
    'django_seed',
]
```

E depois simplesmente rode o comando

```
python manage.py seed travel --number=15
```

onde `travel` é o nome da nossa app neste projeto.


# Dica 42 - Custom context processors

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/VidyZ5gqSRY)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

O **Custom context processors** é um recurso que nos fornece objetos globais que podemos usar em qualquer parte da nossa aplicação.

```python
cat << EOF > myproject/travel/context_processors.py
from .models import Travel


def travel_count(request):
    travel = Travel.objects.all()
    context = {'total_travel': travel.count()}
    return context

EOF
```

Edite `settings.py`:

```python
TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                # apps
                'myproject.travel.context_processors.travel_count',
            ],
        },
    },
]
```

E em `nav.html`

```html
<li class="nav-item">
    <a class="nav-link" href="{\% url 'travel:travel_list' %}">
        Viagens
        <span class="badge badge-warning">{{ total_travel }}</span>
    </a>
</li>
```


# Dica 43 - django-admin-rangefilter

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/GalpHaLJK3Q)

<https://github.com/silentsokolov/django-admin-rangefilter>

```
pip install django-admin-rangefilter
```

Em `settings.py`

```python
INSTALLED_APPS = [
    ...
    'rangefilter',
    ...
]
```

Em `core/admin.py` ￼

```python
from rangefilter.filters import DateRangeFilter, DateTimeRangeFilter


@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_filter = (
        ('published_date', DateRangeFilter),
        ('modified', DateTimeRangeFilter),
    )
```


# Dica 44 - Django: F() expression

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/YDCeA3rNcno)

O [F() expression](https://docs.djangoproject.com/en/3.2/ref/models/expressions/#f-expressions) é uma expressão que retorna a representação do valor do campo, ou seja, é o valor do campo propriamente dito.

Vamos criar 3 novas apps:

```
cd myproject
python ../manage.py event
python ../manage.py product
python ../manage.py ecommerce
```

Edite `event/apps.py`

```python
from django.apps import AppConfig


class EventConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myproject.event'
```

Edite `event/models.py`

```python
from django.db import models


class Room(models.Model):
    name = models.CharField('nome', max_length=100, unique=True)
    num_participants = models.PositiveSmallIntegerField('quantidade de participantes')  # noqa E501
    num_chairs = models.PositiveSmallIntegerField('quantidade de cadeiras')

    class Meta:
        ordering = ('name',)
        verbose_name = 'sala'
        verbose_name_plural = 'salas'

    def __str__(self):
        return self.name
```

Edite `event/admin.py`

```python
from django.contrib import admin

from .models import Room


@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
    list_display = ('name', 'num_participants', 'num_chairs')
    search_fields = ('name',)
```

Rode

```
python manage.py shell_plus
```

```python
# Room.objects.all().delete()
rooms = [
    ('Evento 1', 500, 400),
    ('Evento 2', 500, 550),
    ('Evento 3', 600, 500),
    ('Evento 4', 680, 700),
    ('Evento 5', 1000, 990),
    ('Evento 6', 1200, 500),
    ('Evento 7', 1000, 450),
]

for room in rooms:
    Room.objects.create(name=room[0], num_participants=room[1], num_chairs=room[2])



# Filtra as salas cuja quantidade de participantes seja maior que a quantidade de cadeiras.
rooms = Room.objects.filter(num_participants__gt=F('num_chairs'))

for room in rooms:
    print(room.num_participants, room.num_chairs, room.num_participants - room.num_chairs)

# Calcula a diferença entre participantes e cadeiras.
rooms = Room.objects.annotate(difference=F('num_participants') - F('num_chairs')).filter(num_participants__gt=F('num_chairs'))

for room in rooms:
    print(room.num_participants, room.num_chairs, room.difference)


# Filtra as salas cuja quantidade de participantes seja maior que o dobro da quantidade de cadeiras.
rooms = Room.objects.filter(num_participants__gt=F('num_chairs') * 2)

for room in rooms:
    print(room.num_participants, room.num_chairs, room.num_chairs * 2)
```

Edite `product/apps.py`

```python
from django.apps import AppConfig


class ProductConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myproject.product'
```

Edite `product/models.py`

```python
from django.db import models


class Product(models.Model):
    title = models.CharField('título', max_length=100, unique=True)
    price = models.DecimalField('preço', max_digits=7, decimal_places=2)
    manufacturing_date = models.DateField('data de fabricação', null=True, blank=True)  # noqa E501
    due_date = models.DateField('data de vencimento', null=True, blank=True)

    class Meta:
        ordering = ('title',)
        verbose_name = 'produto'
        verbose_name_plural = 'produtos'

    def __str__(self):
        return self.title
```

Edite `product/admin.py`

```python
from django.contrib import admin

from .models import Product


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('title', 'price', 'manufacturing_date', 'due_date')
    search_fields = ('title',)
```

Rode

```
python manage.py shell_plus
```

```python
products = [
    {
        'title': 'queijo fresco',
        'price': '8.99',
        'manufacturing_date': '2021-08-01',
        'due_date': '2021-08-03',
    },
    {
        'title': 'sorvete de manga',
        'price': '12',
        'manufacturing_date': '2021-08-10',
        'due_date': '2021-10-10',
    },
    {
        'title': 'leite integral',
        'price': '5.12',
        'manufacturing_date': '2021-08-02',
        'due_date': '2021-08-06',
    },
    {
        'title': 'pão de forma',
        'price': '7.45',
        'manufacturing_date': '2021-08-01',
        'due_date': '2021-08-06',
    },
]

Product.objects.all().delete()
aux_list = []
for item in products:
    obj = Product(
        title=item['title'],
        price=item['price'],
        manufacturing_date=item['manufacturing_date'],
        due_date=item['due_date'],
    )
    aux_list.append(obj)

Product.objects.bulk_create(aux_list)


from datetime import timedelta

# Retorna os produtos cuja data de validade seja inferior a 5 dias.
Product.objects.annotate(expiration_days=F('due_date') - F('manufacturing_date')).filter(expiration_days__lt=timedelta(days=5))

products = Product.objects.all()

for product in products:
    print(product.title, (product.due_date - product.manufacturing_date).days)
```

Edite `ecommerce/apps.py`

```python
from django.apps import AppConfig


class EcommerceConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myproject.ecommerce'
```

Edite `ecommerce/models.py`

```python
from django.db import models

from myproject.core.models import TimeStampedModel
from myproject.product.models import Product


class Order(TimeStampedModel):
    nf = models.CharField('nota fiscal', max_length=100, unique=True)

    class Meta:
        ordering = ('nf',)
        verbose_name = 'ordem de compra'
        verbose_name_plural = 'ordens de compra'

    def __str__(self):
        return self.nf


class OrderItems(models.Model):
    order = models.ForeignKey(
        Order,
        on_delete=models.SET_NULL,
        verbose_name='ordem',
        related_name='order_items',
        null=True,
        blank=True
    )
    product = models.ForeignKey(
        Product,
        on_delete=models.SET_NULL,
        verbose_name='produto',
        related_name='product_items',
        null=True,
        blank=True
    )
    quantity = models.PositiveIntegerField('quantidade')
    price = models.DecimalField('preço', max_digits=7, decimal_places=2)

    class Meta:
        ordering = ('pk',)

    def __str__(self):
        return f'{self.pk} - {self.order.pk} - {self.product}'
```

Edite `ecommerce/admin.py`

```python
from django.contrib import admin

from .models import Order, OrderItems

admin.site.register(Order)


@admin.register(OrderItems)
class OrderItemsAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'quantity', 'price')
```

Rode

```
python manage.py seed product ecommerce --number=10
```

Depois abra o shell\_plus

```
python manage.py shell_plus
```

```python
# Retorna o subtotal do preço vezes a quantidade de cada produto.
items = OrderItems.objects.annotate(subtotal=F('product__price') * F('quantity'))

for item in items:
    print(f'{item.quantity}\t{item.product.price}\t{round(item.subtotal, 2)}')
```


# Dica 45 - DRF: Scaffold django apis - Django REST framework

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/UOW0CaFayFo)

Github: <https://github.com/rg3915/drf-example>

<https://github.com/Abdenasser/dr_scaffold>

<https://www.abdenasser.com/scaffold-django-apis>

dr-scaffold é uma lib para criar models e uma API simples em Django REST framework.

### Experimentando e criando um projeto do zero

```
python -m venv .venv
source .venv/bin/activate
pip install dr-scaffold djangorestframework django-extensions python-decouple
```

Crie um arquivo `.env`

```
cat << EOF > .env
SECRET_KEY=my-super-secret-key-dev-only
EOF
```

Crie um novo projeto.

```
django-admin startproject backend .
```

Edite `settings.py`

```python
# settings.py

from decouple import config

SECRET_KEY = config('SECRET_KEY')

INSTALLED_APPS = [
    ...
    'rest_framework',
    'dr_scaffold',
]
```

#### Exemplo 1

Rodando o comando `dr_scaffold`

```
python manage.py dr_scaffold blog Author name:charfield

python manage.py dr_scaffold blog Post body:textfield author:foreignkey:Author
```

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'dr_scaffold',
    'blog',  # <--
]
```

Edite `urls.py`

```python
# urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
]
```

```
python manage.py makemigrations
python manage.py migrate
```

#### Exemplo 2

```
python manage.py dr_scaffold product Product title:charfield price:decimalfield

python manage.py dr_scaffold ecommerce Order nf:charfield

python manage.py dr_scaffold ecommerce OrderItems \
order:foreignkey:Order \
product:foreignkey:Product \
quantity:integerfield \
price:decimalfield
```

Edite `settings.py`

```python
INSTALLED_APPS = [
    ...
    'rest_framework',
    'dr_scaffold',
    'blog',  # <--
    'product',  # <--
    'ecommerce',  # <--
]

```

Edite `urls.py`

```python
...
path('product/', include('product.urls')),
path('ecommerce/', include('ecommerce.urls')),
...
```

Não se esqueça de editar `ecommerce/models.py`

```python
from product.models import Product
```

```
python manage.py makemigrations
python manage.py migrate
```


# Dica 46 - DRF: drf-yasg - Yet another Swagger generator

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/TytDfV3PVFU)

Github: <https://github.com/rg3915/drf-example>

Doc: <https://drf-yasg.readthedocs.io/en/stable/>

Doc: <https://github.com/axnsan12/drf-yasg/>

[drf-yasg](https://github.com/axnsan12/drf-yasg/) é uma outra biblioteca para gerar a documentação com Swagger e reDoc.

```
pip install -U drf-yasg

pip freeze | grep drf-yasg >> requirements.txt
```

Edite `settings.py`

```python
INSTALLED_APPS = [
   ...
   'django.contrib.staticfiles',  # required for serving swagger ui's css/js files
   'drf_yasg',
   ...
]
```

Edite `urls.py`

```python
from django.contrib import admin
from django.urls import include, path
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions

schema_view = get_schema_view(
    openapi.Info(
        title="Snippets API",
        default_version='v1',
        description="Test description",
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="contact@snippets.local"),
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=[permissions.AllowAny],
)

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('product/', include('product.urls')),
    path('ecommerce/', include('ecommerce.urls')),
    path('admin/', admin.site.urls),
]

# swagger
urlpatterns += [
   path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),  # noqa E501
   path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),  # noqa E501
]
```


# Dica 47 - DRF: djoser - Django REST framework

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/HUtG2Eg47Gw)

Github: <https://github.com/rg3915/drf-example>

<https://djoser.readthedocs.io/en/latest/>

```
pip install -U djoser
pip freeze | grep djoser >> requirements.txt
```

Configure `INSTALLED_APPS`

```python
INSTALLED_APPS = (
    'django.contrib.auth',
    (...),
    'rest_framework',
    'rest_framework.authtoken',  # <-- rode ./manage.py migrate
    'djoser',  # <--
    (...),
)

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

```

Configure `urls.py`

```python
# djoser
urlpatterns += [
    path('api/v1/', include('djoser.urls')),
    path('api/v1/auth/', include('djoser.urls.authtoken')),
]
```

```
python manage.py migrate
python manage.py drf_create_token admin

token d7643a4710c7e19915df7d5e3d82f70cb07998ba  # o seu será um novo
```

Veja no video como rodar no **Postman** e no **Swagger**.

Exemplos com curl

```
# Cria novo usuário
curl -X POST http://127.0.0.1:8000/api/v1/users/ --data 'username=djoser&password=api127rg'

# Login
curl -X POST http://127.0.0.1:8000/api/v1/auth/token/login/ --data 'username=djoser&password=api127rg'

# Informações do usuário
curl -X GET http://127.0.0.1:8000/api/v1/users/me/ -H 'Authorization: Token d7643a4710c7e19915df7d5e3d82f70cb07998ba'  # o seu será um novo

# Logout
curl -X GET http://127.0.0.1:8000/api/v1/auth/token/logout/ -H 'Authorization: Token d7643a4710c7e19915df7d5e3d82f70cb07998ba'  # o seu será um novo
```

Quando faz o logout ele apaga o token, e só gera um novo quando você fizer login novamente.


# Dica 48 - DRF: Reset de Senha com djoser - Django REST framework

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/BilRdaQXX8U)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Github: <https://github.com/rg3915/drf-example>

### MailHog

Rode o [MailHog](https://github.com/mailhog/MailHog) usando Docker.

`docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog`

Endpoints

```
/api/v1/auth/token/login/
/api/v1/users/reset_password/
/api/v1/users/reset_password_confirm/
```

![reset\_password](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-75ea5053fa2e7ab171b38b8f77503a3f4336094c%2Freset_password.png?alt=media)

Edite `settings.py`

```python
DJOSER = {
    'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}',
}

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
EMAIL_HOST = config('EMAIL_HOST', '0.0.0.0')  # localhost
EMAIL_PORT = config('EMAIL_PORT', 1025, cast=int)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
```

### Página em pt-br

Edite `settings.py`

```python
LANGUAGE_CODE = 'pt-br'
```

### Página com template personalizado

Edite `settings.py`

```python
DJOSER = {
    'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}',
    'EMAIL': {
        'password_reset': 'accounts.email.PasswordResetEmail'
    }
}

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR,
            BASE_DIR.joinpath('templates')
        ],
        ...
    }
]
```

Crie uma nova app

```
python manage.py startapp accounts
rm -f accounts/{admin,models,tests,views}.py
```

Crie um arquivo `email.py`

```python
cat << EOF > accounts/email.py
from djoser import email


class PasswordResetEmail(email.PasswordResetEmail):
    template_name = 'accounts/email/password_reset.html'

EOF
```

Crie o template de e-mail

```
mkdir -p accounts/templates/accounts/email
touch accounts/templates/accounts/email/password_reset.html
```

<https://github.com/sunscrapers/djoser/blob/master/djoser/templates/email/password\\_reset.html>

Edite `password_reset.html`

```html
{\% block text_body %}
Olá {{ user.first_name }},
...

{\% block html_body %}
Olá {{ user.first_name }},
...
```

### Postman

#### Login

POST: <http://localhost:8000/api/v1/auth/token/login/>

```
{
    "username": "huguinho",
    "password": "d"
}
```

#### Reset

POST: <http://localhost:8000/api/v1/users/reset\\_password/>

```
{
    "email": "huguinho@email.com"
}
```

* Não precisa de Token Authorization.

#### Reset Password Confirm

POST: <http://localhost:8000/api/v1/users/reset\\_password\\_confirm/>

```
{
    "uid": "MQ",
    "token": "at61wx-d98ea2d93ae43ba571252177750c4de8",
    "new_password": "my_super_new_password123"
}
```

* Não precisa de Token Authorization.

Se em settings você definir `PASSWORD_RESET_CONFIRM_RETYPE=True` então você precisa passar `re_new_password`.

```
{
    "uid": "MQ",
    "token": "at61wx-d98ea2d93ae43ba571252177750c4de8",
    "new_password": "my_super_new_password123"
    "re_new_password": "my_super_new_password123"
}
```


# Dica 49 - DRF: Autenticação via JWT com djoser - Django REST framework

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/dOomllYxj9E)

Github: <https://github.com/rg3915/drf-example>

<https://djoser.readthedocs.io/en/latest/jwt_endpoints.html>

JSON Web Token Authentication: <https://djoser.readthedocs.io/en/latest/authentication_backends.html#json-web-token-authentication>

`pip install djoser djangorestframework-simplejwt`

![jwt.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-28aa80349770852aa22d7267834d26d637293366%2Fjwt.png?alt=media\&token=69dc07d0-34bd-4e8f-a17c-709df2c9f5f2)

```python
# settings.py
from datetime import timedelta

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',  # <--
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}


SIMPLE_JWT = {
    "AUTH_HEADER_TYPES": ("Bearer",),  # na doc está JWT mas pode mudar pra Bearer.
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    # "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
}
```

```python
# urls.py
urlpatterns += [
    path('api/v1/', include('djoser.urls')),
    path('api/v1/auth/', include('djoser.urls.authtoken')),
    path('api/v1/auth/', include('djoser.urls.jwt')),  # <--
]
```

```python
# views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

from product.models import Product
from product.serializers import ProductSerializer


class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = (IsAuthenticated,)  # <--
```

```
curl -X POST http://127.0.0.1:8000/api/v1/auth/token/login/ --data 'username=admin&password=d'

curl -X POST http://127.0.0.1:8000/api/v1/auth/jwt/create/ --data 'username=admin&password=d'
```

Pegar access

```
# atualizar
curl -X POST \
http://127.0.0.1:8000/api/v1/auth/jwt/refresh/ \
-H 'Content-Type: application/json' \
--data '{"refresh": ""}'

curl -X GET \
http://127.0.0.1:8000/product/products/ \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLC...'
```

### Consumindo a API com Python

```python
'''
Usage:

python consumer.py -u usuario -p senha
'''
from pprint import pprint
from typing import Dict

import click
import requests
from requests.auth import HTTPBasicAuth

BASE_URL = 'http://localhost:8000/api/v1/'


def fetch_token(session, endpoint, username, password):
    '''
    Faz autenticação do usuário.
    '''
    # headers = {'Content-type': 'application/json'}  # Não precisou
    data = {
        'username': username,
        'password': password,
    }
    with session.post(
        endpoint,
        auth=HTTPBasicAuth(username, password),
        # headers=headers,  # Não precisou
        data=data
    ) as response:
        return response.json()


def get_token(username: str, password: str) -> Dict[str, str]:
    '''
    Pega o access_token do usuário logado.
    '''
    with requests.Session() as session:
        endpoint = f'{BASE_URL}auth/jwt/create/'
        response = fetch_token(session, endpoint, username, password)
        data = {
            'access_token': response['access'],
        }
        return data


def fetch(session, endpoint, access_token):
    '''
    Faz a autenticação usando JWT.
    '''
    headers = {'Authorization': f'Bearer {access_token}'}
    with session.get(endpoint, headers=headers) as response:
        return response.json()


def post_product(session, endpoint, access_token, title, price):
    '''
    Salva o produto.
    '''
    headers = {'Authorization': f'Bearer {access_token}'}
    data = {
        'title': title,
        'price': price,
    }
    with session.post(endpoint, headers=headers, data=data) as response:
        print(response)
        pprint(response.json())


@click.command()
@click.option('--username', '-u', prompt='username', help='Type the username.')
@click.option('--password', '-p', prompt='password', help='Type the password.')
@click.option('--title', '-t', help='Type the title.')
@click.option('--price', '-pr', help='Type the price.')
def main(username, password, title=None, price=None):
    '''
    Consumindo a lista de produtos.
    '''
    token = get_token(username, password)
    access_token = token['access_token']
    with requests.Session() as session:
        endpoint = 'http://127.0.0.1:8000/product/products/'
        response = fetch(session, endpoint, access_token)
        pprint(response)

        if title and price:
            print(f'Salvando produto: {title}')
            post_product(session, endpoint, access_token, title, price)


if __name__ == '__main__':
    print('Produtos')
    main()

```


# Dica 50 - DRF: Django CORS headers

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/2SyQ9xXdMvw)

Github: <https://github.com/rg3915/drf-example>

<https://pt.wikipedia.org/wiki/Cross-origin_resource_sharing>

<https://github.com/adamchainz/django-cors-headers>

```
python -m pip install django-cors-headers
pip freeze | grep django-cors-headers >> requirements.txt
```

Edite `settings.py`

```python
CORS_ALLOWED_ORIGINS = [
    'http://localhost:8080',
]

INSTALLED_APPS = [
    ...,
    'corsheaders',
    ...,
]

MIDDLEWARE = [
    ...,
    # corsheaders
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...,
]

```

```
# Se necessário
python manage.py drf_create_token huguinho
```

### Frontend

```
npm install -g @vue/cli
vue create frontend

# adicione Router, Vuex e sass-loader (CSS Pre-processors) na instalação
Tire o Linter / Formatter
Marque também Sass/SCSS (with node-sass)

cd frontend
npm install axios bulma bulma-toast
npm audit fix
```

```js
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'

axios.defaults.baseURL = 'http://127.0.0.1:8000'

createApp(App).use(store).use(router, axios).mount('#app')
```

```js
// App.vue
// ...
<router-link to="/login">Login</router-link>
// ...
<style lang="scss">
@import '../node_modules/bulma';
// ...
</style>
```

Edite router/index.js

```
import Login from '../views/Login.vue'

{
  path: '/login',
  name: 'Login',
  component: Login
}
```

touch src/views/Login.vue

```js
<template>
  <div class="container">
    <div class="columns">
      <div class="column is-4 is-offset-4">
        <h1 class="title">Login</h1>

        <form @submit.prevent="submitForm">

          <div class="field">
            <label>Usuário</label>
            <div class="control">
              <input type="text" name="username" class="input" v-model="username" autofocus>
            </div>
          </div>

          <div class="field">
            <label>Senha</label>
            <div class="control">
              <input type="password" name="password" class="input" v-model="password">
            </div>
          </div>

          <div class="notification is-danger" v-if="errors.length">
            <p v-for="error in errors" v-bind:key="error">{{ error }}</p>
          </div>

          <div class="field">
            <div class="control">
              <button class="button is-success">Entrar</button>
            </div>
          </div>

        </form>

      </div>
    </div>
  </div>
</template>

<script>
    import axios from 'axios'

    export default {
      name: 'Login',
      data() {
        return {
          username: '',
          password: '',
          errors: []
        }
      },
      methods: {
        async submitForm() {
          axios.defaults.headers.common['Authorization'] = ''
          localStorage.removeItem('token')
          const formData = {
            username: this.username,
            password: this.password
          }
          await axios
            .post('/api/v1/auth/token/login/', formData)
            .then(response => {
              const token = response.data.auth_token
              axios.defaults.headers.common['Authorization'] = 'Token ' + token
              localStorage.setItem('token', token)
            })
            .catch(error => {
              if (error.response) {
                for (const property in error.response.data) {
                  this.errors.push(`${property}: ${error.response.data[property]}`)
                }
              } else if (error.message) {
                this.errors.push('Algo deu errado. Por favor tente novamente!')
              }
            })
        }
      }
    }
</script>
```


# Dica 51 - DRF: paginação

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/UqES8tphzsQ)

Github: <https://github.com/rg3915/drf-example>

Doc: <https://www.django-rest-framework.org/api-guide/pagination>

Vamos precisar do [django-seed](https://www.dicas-de-django.com.br/41-django-seed)

```
pip install django-seed
```

Editar `settings.py`

```python
INSTALLED_APPS = [
    ...
    'django_seed',
```

Rodar o comando

```
python manage.py seed blog --number=250
```

Editar `blog/models.py`

```python
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        verbose_name_plural = "Authors"

    def __str__(self):
        return self.name


class Post(models.Model):
    body = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True)
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "Posts"

    def __str__(self):
        return self.body
```

Editar `blog/views.py`

```python
from rest_framework.permissions import AllowAny

class AuthorViewSet(viewsets.ModelViewSet):
    ...
    permission_classes = (AllowAny,)

class PostViewSet(viewsets.ModelViewSet):
    ...
    permission_classes = (AllowAny,)
```

Editar `blog/serializers.py`

```python
class AuthorSerializer(serializers.ModelSerializer):
    ...

class PostSerializer(serializers.ModelSerializer):
    ...
```

## LimitOffsetPagination

Editar `settings.py`

```python
REST_FRAMEWORK = {
    ...
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 5
}
```

## PageNumberPagination

Editar `settings.py`

```python
REST_FRAMEWORK = {
    ...
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 5
}
```

## Paginação personalizada global (Custom Pagination)

> Criar app core

```
rm -f core/{admin,models,tests,views}.py
rm -rf core/migrations
touch core/pagination.py
```

Editar `core/pagination.py`

```python
from rest_framework.pagination import PageNumberPagination


class StandardResultsSetPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100
```

Editar `settings.py`

```python
REST_FRAMEWORK = {
    ...
    'DEFAULT_PAGINATION_CLASS': 'core.pagination.StandardResultsSetPagination',
    'PAGE_SIZE': 5
}
```

## Paginação personalizada para o blog

Editar `blog/pagination.py`

```python
from rest_framework.pagination import PageNumberPagination


class CustomBlogResultsSetPagination(PageNumberPagination):
    page_size = 7
    page_size_query_param = 'page_size'
    max_page_size = 70
```

Editar `blog/views.py`

```python
from blog.pagination import CustomBlogResultsSetPagination

class PostViewSet(viewsets.ModelViewSet):
    ...
    pagination_class = CustomBlogResultsSetPagination
```

## Cursor Pagination

Editar `settings.py`

```python
REST_FRAMEWORK = {
    ...
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
    'PAGE_SIZE': 5
}
```

> Requer um campo com o nome `created` no seu modelo.


# Dica 52 - DRF: django-filter

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/J8mKLw_Txok)

Github: <https://github.com/rg3915/drf-example>

Doc: <https://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend>

Doc: <https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html#integration-with-drf>

## Filtrando a queryset

Editar `blog/models.py`

```python
# blog/models.py
from django.contrib.auth.models import User
from django.db import models


class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30, null=True)

    class Meta:
        verbose_name_plural = "Authors"

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


class Post(models.Model):
    title = models.CharField(max_length=30)
    body = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True)
    created_by = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        verbose_name='criado por',
        null=True
    )
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "Posts"

    def __str__(self):
        return self.title
```

Editar `blog/admin.py`

```python
# blog/admin.py
from django.contrib import admin

from blog.models import Author, Post


@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('__str__',)


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'created_by')
```

Editar `blog/views.py`

```python
# blog/views.py
from rest_framework.permissions import AllowAny, IsAuthenticated

class PostViewSet(viewsets.ModelViewSet):
    # queryset = Post.objects.all()
    queryset = Post.objects.filter(created_by__username='regis')
    serializer_class = PostSerializer
    # permission_classes = (AllowAny,)
    permission_classes = (IsAuthenticated,)
    # pagination_class = CustomBlogResultsSetPagination
```

## Filtrando pelo usuário logado

Editar `blog/views.py`

```python
# blog/views.py
class PostViewSet(viewsets.ModelViewSet):
    # queryset = Post.objects.all()
    # queryset = Post.objects.filter(created_by__username='regis')
    serializer_class = PostSerializer
    # permission_classes = (AllowAny,)
    permission_classes = (IsAuthenticated,)
    # pagination_class = CustomBlogResultsSetPagination

    def get_queryset(self):
        user = self.request.user
        return Post.objects.filter(created_by=user)
```

Erro:

```
AssertionError: `basename` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
```

Editar `blog/urls.py`

```python
# blog/urls.py
...
router.register(r'authors', AuthorViewSet, basename='Author')
router.register(r'posts', PostViewSet, basename='Post')
```

## Filtrando a partir de query parameters

Editar `settings.py`

```python
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
```

Editar `blog/views.py`

```python
# blog/views.py
class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        queryset = Post.objects.all()
        username = self.request.query_params.get('username')

        if username is not None:
            queryset = queryset.filter(created_by__username=username)

        title = self.request.query_params.get('title')

        if title is not None:
            queryset = queryset.filter(title__icontains=title)

        return queryset
```

## Filtro Genérico django-filter

<https://django-filter.readthedocs.io/en/stable/guide/rest\\_framework.html#integration-with-drf>

```
pip install django-filter

pip freeze | grep django-filter >> requirements.txt
```

Editar `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    # 3rd apps
    'django_filters',
    ...
]

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
```

Editar `blog/views.py`

```python
# blog/views.py
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)
    filterset_fields = ('title', 'body')

# comentar def get_queryset(self)
```

Filtra pelo texto completo.

## Adicionando filtro específico com filterset\_class

Editar `blog/filters.py`

```python
# blog/filters.py
from django_filters import rest_framework as filters

from blog.models import Post


class PostFilter(filters.FilterSet):
    title = filters.CharFilter(field_name="title", lookup_expr='icontains')
    body = filters.CharFilter(field_name="body", lookup_expr='icontains')

    class Meta:
        model = Post
        fields = ('title', 'body')
```

Editar `blog/views.py`

```python
# blog/views.py
from blog.filters import PostFilter


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)
    # filterset_fields = ('title', 'body')
    filterset_class = PostFilter
```

## Campo de busca

Editar `blog/views.py`

```python
# blog/views.py
from rest_framework.filters import SearchFilter

class AuthorViewSet(viewsets.ModelViewSet):
    ...
    filter_backends = (SearchFilter,)
    search_fields = ('first_name', 'last_name')
```


# Dica 53 - DRF: Criando subrota com action

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/6IS8KfzvD74)

Github: <https://github.com/rg3915/drf-example>

Doc: <https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing>

Doc: <https://www.django-rest-framework.org/api-guide/routers/#routing-for-extra-actions>

Editar `blog/models.py`

```python
class Post(models.Model):
    ...
    like = models.BooleanField(null=True)
```

Editar `blog/views.py`

```python
from rest_framework.decorators import action
from rest_framework.response import Response

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated,)

    @action(detail=True, methods=['put'])
    def like(self, request, pk=None):
        '''
        Marca Like = True
        '''
        post_obj = self.get_object()
        post_obj.like = True
        post_obj.save()
        serializer = self.get_serializer(post_obj)
        return Response(serializer.data)

    @action(detail=True, methods=['put'])
    def unlike(self, request, pk=None):
        '''
        Marca Like = False
        '''
        post_obj = self.get_object()
        post_obj.like = False
        post_obj.save()
        serializer = self.get_serializer(post_obj)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def my_posts(self, request, pk=None):
        '''
        Retorna somente os meus posts.
        '''
        user = self.request.user
        # posts = Post.objects.filter(created_by=user)
        posts = self.get_queryset().filter(created_by=user)

        page = self.paginate_queryset(posts)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(posts, many=True)
        return Response(serializer.data)
```

Editar `blog/admin.py`

```python
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'created_by', 'like')
    list_filter = ('like',)
```

As novas rotas são:

```
/blog/posts/<pk>/like/
/blog/posts/<pk>/unlike/
/blog/posts/my_posts/
```


# 54-django-extensions-mais-comandos

## Dica 54 - django-extensions - mais comandos

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/pguupq-s70M)

Github: <https://github.com/rg3915/dicas-de-django>

Doc: <https://django-extensions.readthedocs.io/en/latest/>

## [Command Extensions](https://django-extensions.readthedocs.io/en/latest/command_extensions.html)

Veremos mais comandos do django-extensions.

```
pip install -U django-extensions
```

```python
# settings.py
INSTALLED_APPS = (
    ...
    'django_extensions',
)
```

### shell\_plus

Roda o shell do Django importando todos os pacotes essenciais.

```
python manage.py shell_plus
```

### admin\_generator

Gera uma classe no Admin para a app selecionada.

```
python manage.py admin_generator product
python manage.py admin_generator product >> myproject/product/admin.py
```

### clean\_pyc

Remove todos os arquivos `*.pyc`

```
python manage.py clean_pyc
```

### create\_command

Cria um novo comando do Django.

```
python manage.py create_command core -n novocomando
```

### create\_template\_tags

Cria um novo `template_tags`.

```
python manage.py create_template_tags core
python manage.py create_template_tags core -n lorem_tags
```

### show\_template\_tags

Lista todos os `template_tags`.

```
python manage.py show_template_tags
```

### generate\_password

Gera uma senha randômica.

```
python manage.py generate_password
python manage.py generate_password --length 32
```

### generate\_secret\_key

Gera uma chave secreta.

```
python manage.py generate_secret_key
```

### graph\_models

Link na descrição do video

<https://youtu.be/99dOVsDBUxg>

### list\_model\_info

Lista as informações de um modelo.

```
python manage.py list_model_info --model product.Product
python manage.py list_model_info --model product.Product --field-class
```

### list\_signals

Lista os sinais do projeto.

```
python manage.py list_signals
```

### print\_settings

Retorna as informações do `settings`.

```
python manage.py print_settings
```

### show\_urls

Lista todas as urls do projeto.

```
python manage.py show_urls
```


# Dica 55 - Rodando Django em https localmente com runserver\_plus

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/4nI3lcUAeC4)

Github: <https://github.com/rg3915/dicas-de-django>

```
pip install Werkzeug
```

```
python manage.py runserver_plus --print-sql
```

## HTTPS

```
pip install pyOpenSSL
python manage.py runserver_plus --cert-file /tmp/cert.crt
```

Entre em <https://localhost:8000>


# Dica 56 - Django inlineformset\_factory + HTMX

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/66_FQxopiBY)

Este video está disponível nas playlist:

* Dicas de Django
* HTMX
* Tutorial Django
* Django - assuntos diversos

Github: <https://github.com/rg3915/django-inlineformset-tutorial>

Todo o passo a passo está em <https://github.com/rg3915/django-inlineformset-tutorial#passo-a-passo>.


# Dica 57 - Criando API com Django SEM DRF

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/HLdREjEYVMU)

Github: <https://github.com/rg3915/django-api-without-drf>


# Dica 58 - Rodando PostgreSQL com Docker + Portainer + pgAdmin + Django local para desenvolvimento

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/aWZDFKJz7X8)

Github: <https://github.com/rg3915/django-postgresql-docker>


# Dica 59 - Django: Busca por palavras acentuadas ou sem acento

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/rMW562J6tGE)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Github do projeto: <https://github.com/rg3915/django-postgresql-docker>

Busca sem acento

<https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/lookups/>

## Rodar o PostgreSQL via Docker

```
docker-compose up -d

docker container exec -it db psql

\c db

CREATE EXTENSION unaccent;

\q
```

Edite `settings.py`

```python
INSTALLED_APPS = [
    ...
    'django.contrib.postgres',
    ...
]
```

## Adicionar artigos pelo shell\_plus

`python manage.py shell_plus`

```python
artigos = [
    ('Filmes', 'Conheça os filmes de ação de 2021'),
    ('Gastronomia', 'Conheça os melhores pratos da França'),
    ('Livros', 'Inteligência emocional'),
    ('Café', 'Café frappé ou café frapê é um café solúvel gelado'),
]

aux_list = []
for artigo in artigos:
    obj = Article(
        title=artigo[0],
        subtitle=artigo[1]
    )
    aux_list.append(obj)

Article.objects.bulk_create(aux_list)
```

## Criar campo de busca

Edite `core/article_list.html`

```html
<h1>Lista de {\% model_name_plural model %}</h1>

<form class="form-inline my-2">
  <label>Busca</label>
  <input class="form-control ml-sm-2" name="search" type="text"/>
  <button class="btn btn-primary my-2 my-sm-0" type="submit">OK</button>
  <a href=".">Limpar</a>
</form>

```

## Fazendo a busca com ou sem acento

Edite `core/views.py`

```python
from django.db.models import Q


def article_list(request):
    template_name = 'core/article_list.html'
    object_list = Article.objects.all()

    search = request.GET.get('search')

    if search:
        object_list = object_list.filter(
            Q(title__unaccent__icontains=search) |
            Q(subtitle__unaccent__icontains=search)
        )
    ...
```

Reinicie o servidor.

`python manage.py runserver`


# Dica 60 - Django: Adicionando atributos extras no formulário

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/s8U_YBExsQ0)

Repo: <https://gitlab.com/rg3915/exame-inline>

Veja como adicionar atributos extras no formulário.

```python
class CareItemsForm(forms.ModelForm):
    id = forms.IntegerField()

    class Meta:
        model = CareItems
        fields = ('care', 'id', 'exam', 'is_done')

    def __init__(self, *args, **kwargs):
        super(CareItemsForm, self).__init__(*args, **kwargs)

        ...

        pk = reverse('exam:care_update_exam', kwargs={'pk': self.instance.pk})
        self.fields['is_done'].widget.attrs['hx-get'] = f'{pk}'
        self.fields['is_done'].widget.attrs['hx-swap'] = f'none'

```


# Dica 01 - Criando Issues com API do Github (Linux)

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/P0PCq3dF3cs)

```
mkdir cli
touch cli/create_issue.py

touch .env.sample
touch requirements.txt
```

## Instalação

```
python -m venv .venv
source .venv/bin/activate

pip install click python-decouple requests
pip freeze | grep -E 'click|python-decouple|requests' > requirements.txt
```

## Variáveis de Ambiente

Pegue o token no Github, e defina

```
# .env
REPO_OWNER=rg3915
REPO_NAME=dicas-de-django
TOKEN=********************
```

O conteúdo de `create_issue.py` é

```python
# create_issue.py
import click
import requests
from decouple import config

'''
https://docs.github.com/en/rest/reference/issues#create-an-issue

python cli/create_issue.py \
--title='' \
--body='' \
--labels='feature'
'''

# O repositório para adicionar a issue
REPO_OWNER = config('REPO_OWNER')
REPO_NAME = config('REPO_NAME')
TOKEN = config('TOKEN')


def write_file(filename, number, title, description, labels):
    labels = ', '.join(labels).strip()
    with open(filename, 'a') as f:
        f.write(f'\n---\n\n')
        f.write(f'[ ] {number} - {title}\n')
        f.write(f'    {labels}\n\n')

        if description:
            f.write(f'    {description}\n\n')

        f.write(f"    make lint; g add . ; g co -m '{title}. close #{number}'; g push\n")


@click.command()
@click.option('--title', prompt='Title', help='Digite o título.')
@click.option('--body', prompt='Description', help='Digite a descrição.')
# @click.option('--assignee', prompt='Assignee', help='Digite o nome da pessoa a ser associada.')
@click.option('--labels', prompt='Labels', help='Digite as labels.')
def make_github_issue(title, body=None, assignee=None, milestone=None, labels=None):
    '''
    Cria issue no github.com.
    '''
    url = f'https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/issues'
    headers = {
        "Authorization": f"token {TOKEN}",
    }
    labels = labels.split(',')

    # Cria a issue
    issue = {
        "title": title,
        "body": body,
        "labels": labels
    }
    if assignee:
        issue['assignees'] = [assignee]

    # Adiciona a issue no repositório
    req = requests.post(url, headers=headers, json=issue)

    if req.status_code == 201:
        print(f'Successfully created Issue "{title}"')
        number = req.json()['number']
        description = body

        filename = '/home/seu-usuario/tarefas.txt'
        write_file(filename, number, title, description, labels)

    else:
        print(f'Could not create Issue "{title}"')


if __name__ == '__main__':
    make_github_issue()
```

Então digite

```
python cli/create_issue.py \
--title='' \
--body='' \
--labels='feature'
```


# Dica 02 - Usando http.server

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/D4FeNPq8UTY)

Crie um `public/index.html`.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
  <link rel="shortcut icon" href="http://getskeleton.com/dist/images/favicon.png">
  <title>Skeleton CSS</title>

  <!-- Skeleton -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
</head>
<body>
  <h1>Skeleton template example</h1>
  <p>Esta página está usando <a href="http://getskeleton.com/">skeleton CSS</a>.</p>
  <button class="button-primary">Botão</button>
</body>
</html>
```

E simplesmente digite

```
python -m http.server 8000
```

## Links

<http://getskeleton.com/>

<https://cdnjs.com/libraries/skeleton>


# Dica 03 - Criando um template com Bulma CSS

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/AmzQJm0jPrA)

```
mkdir -p src/pages/bulma
touch src/pages/bulma/index.html

python -m http.server 8000
```

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
  <link rel="shortcut icon" href="https://bulma.io/favicons/favicon-32x32.png?v=201701041855">
  <title>Bulma CSS</title>

  <!-- Bulma -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
  <div class="container">
    <div class="notification is-primary">
      <p>Esta página está usando <a href="https://bulma.io/">Bulma CSS</a>.</p>
    </div>

    <div class="columns">
      <div class="column">
        <!-- card -->
        <!-- https://bulma.io/documentation/components/card/ -->
      </div>

      <div class="column">
        <!-- form -->
        <!-- https://bulma.io/documentation/form/general/#complete-form-example -->
      </div>
    </div>

    <!-- buttons -->
    <!-- https://bulma.io/documentation/elements/button/#colors -->
  </div>
</body>
</html>
```

## Links

<https://bulma.io/>

<https://bulma.io/documentation/components/card/>

<https://bulma.io/documentation/form/general/#complete-form-example>

<https://bulma.io/documentation/elements/button/#colors>


# Dica 04 - Criando um template com Tailwind CSS

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/1aNSwcv5jIM)

```
mkdir -p src/pages/tailwind
touch src/pages/tailwind/index.html

python -m http.server 8000
```

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
  <link rel="shortcut icon" href="https://tailwindcss.com/favicons/favicon-32x32.png?v=3">
  <title>TailwindCSS</title>

  <!-- TailwindCSS -->
  <script src="https://cdn.tailwindcss.com"></script>

  <style>
    .main-content {
      height: calc(100vh - 4rem - 4rem);
    }
  </style>
</head>
<body class="flex flex-col min-h-screen">
  <header class="h-16 bg-slate-800 text-gray-100 flex justify-center items-center">
    <h1 class="text-2xl font-bold">TailwindCSS</h1>
  </header>

  <main class="flex-auto">
    <div class="flex main-content">
      <!-- aside -->
      <div class="flex flex-col relative w-1/6 bg-stone-800 border-r border-gray-200">
        <!-- button -->
        <button class="absolute -right-4 top-12 h-8 w-8 bg-slate-800 text-gray-50 rounded-full border border-gray-200 z-10"><</button>
      </div>
      <!-- main -->
      <div class="relative w-5/6 bg-zinc-800">
        <div class="absolute h-10 w-10 bg-yellow-500 flex items-center justify-center">1</div>
        <div class="absolute right-0 h-10 w-10 bg-yellow-500 flex items-center justify-center">2</div>
        <div class="absolute right-0 bottom-0 h-10 w-10 bg-yellow-500 flex items-center justify-center">3</div>
        <div class="absolute bottom-0 h-10 w-10 bg-yellow-500 flex items-center justify-center">4</div>
        <div class="absolute top-1/2 left-1/2 h-10 w-10 bg-red-500 flex items-center justify-center">5</div>
        <div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 h-10 w-10 bg-yellow-500 flex items-center justify-center">
        5
        <!-- ball -->
        <span class="absolute -right-1.5 -top-1.5 h-3 w-3 bg-blue-500 rounded-full"></span>
        </div>
      </div>
    </div>
  </main>

  <footer class="h-16 bg-slate-800 text-gray-100 flex justify-between items-center px-4 text-lg">
    <h1>Dicas de Django © 2023</h1>
    <h1>by Regis do Python</h1>
  </footer>

</body>
</html>
```

## Links

<https://tailwindcss.com/>

<https://merakiui.com>

<https://tailwind-elements.com/>

<https://www.tailwindtoolbox.com/starter-templates>

<https://flowbite.com/>


# Dica 05 - htmx simples

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/GioIXTokrA8)

```
mkdir -p src/pages/htmx
touch src/pages/htmx/index.html
touch src/pages/htmx/component.html

python -m http.server 8000
```

````html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
  <link rel="shortcut icon" href="https://tailwindcss.com/favicons/favicon-32x32.png?v=3">
  <title>htmx</title>

  <!-- TailwindCSS -->
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
  <div class="flex flex-col sm:w-1/2">
    <div class="overflow-x-auto sm:-mx-6 lg:-mx-8">
      <div class="py-2 inline-block min-w-full sm:px-6 lg:px-8">
        <div class="overflow-hidden">
          <table class="min-w-full">
            <thead class="border-b">
              <tr>
                <th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">
                  Nome
                </th>
              </tr>
            </thead>
            <tbody id="tbody">
              <tr class="border-b">
                <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap">
                  <div class="mb-3 xl:w-96">
                    <input
                      type="text"
                      class="
                        form-control
                        block
                        w-full
                        px-3
                        py-1.5
                        text-base
                        font-normal
                        text-gray-700
                        bg-white bg-clip-padding
                        border border-solid border-gray-300
                        rounded
                        transition
                        ease-in-out
                        m-0
                        focus:text-gray-700
                        focus:bg-white
                        focus:border-blue-600
                        focus:outline-none
                      "/>
                  </div>
                </div>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>

    <div class="flex space-x-2 justify-center">
      <button
        type="button"
        class="
          inline-block
          px-6
          py-2.5
          bg-blue-600
          text-white
          font-medium
          text-xs
          leading-tight
          uppercase
          rounded
          shadow-md
          hover:bg-blue-700
          hover:shadow-lg
          focus:bg-blue-700
          focus:shadow-lg
          focus:outline-none
          focus:ring-0
          active:bg-blue-800
          active:shadow-lg
          transition
          duration-150
          ease-in-out
        "
      >
        Adicionar
      </button>
    </div>
  </div>

</body>
</html>```

```html
<!-- index.html -->
<!-- Segunda parte -->

  <!-- htmx -->
  <script src="https://unpkg.com/htmx.org@1.8.4"></script>

    <button
      hx-get="/src/pages/htmx/component.html"
      hx-target="#tbody"
      hx-swap="beforeend"
    >
      Adicionar
    </button>
````

```html
<!-- component.html -->
<tr class="border-b">
  <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap">
    <div class="mb-3 xl:w-96">
      <input
        type="text"
        class="
          form-control
          block
          w-full
          px-3
          py-1.5
          text-base
          font-normal
          text-gray-700
          bg-white bg-clip-padding
          border border-solid border-gray-300
          rounded
          transition
          ease-in-out
          m-0
          focus:text-gray-700
          focus:bg-white
          focus:border-blue-600
          focus:outline-none
        "/>
    </div>
  </td>
</tr>
```

## Links

<https://htmx.org/>


# Dica 06 - Criando o projeto Django

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/9U9MLXFqjlk)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

```
python -m venv .venv
source .venv/bin/activate

# .venv\Scripts\activate  # Windows

pip install django python-decouple django-extensions
pip freeze | grep -E 'Django|python-decouple|django-extensions' >> requirements.txt
```

Senão digite apenas `pip freeze` e copie os pacotes manualmente.

## Criando o projeto

```
django-admin startproject backend .
```

## Criando a app core

```
cd backend
python ../manage.py startapp core
cd ..
tree backend
```

## Editando settings.py

Rode o comando

```
python contrib/env_gen.py

cat .env
```

Edite `.env`

Edite `.gitignore`

```

.DS_Store

media/
staticfiles/
.idea
.ipynb_checkpoints/
.vscode
*.cast
```

Edite `settings.py`

```python
# settings.py

from pathlib import Path

from decouple import config, Csv


BASE_DIR = Path(__file__).resolve().parent.parent

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool)

ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())

INSTALLED_APPS = [
    ...
    # apps de terceiros
    'django_extensions',
    # minhas apps
    'backend.core',
]

LANGUAGE_CODE = 'pt-br'

TIME_ZONE = 'America/Sao_Paulo'

USE_THOUSAND_SEPARATOR = True

DECIMAL_SEPARATOR = ','

STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR.joinpath('staticfiles')
```

Edite `backend/urls.py`

```python
# backend/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('', include('backend.core.urls', namespace='core')),  # noqa E501
    path('admin/', admin.site.urls),  # noqa E501
]
```

Edite `core/apps.py`

```python
# core/apps.py
from django.apps import AppConfig


class CoreConfig(AppConfig):
    ...
    name = 'backend.core'
```

## Criando a estrutura de templates

Vamos trabalhar com herança de templates.

```
mkdir -p backend/core/templates/includes
touch backend/core/templates/base.html
touch backend/core/templates/index.html
touch backend/core/templates/includes/menu.html
touch backend/core/templates/includes/footer.html
touch backend/core/templates/includes/pagination.html

# ou

# touch backend/core/templates/includes/{menu,footer,pagination}.html
```

Edite `base.html`

```html
<!-- base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
  <link rel="shortcut icon" href="https://www.djangoproject.com/favicon.ico">
  <title>Django</title>

  <!-- TailwindCSS -->
  <script src="https://cdn.tailwindcss.com"></script>

  <link rel="stylesheet" href="{\% static 'css/style.css' %}">

</head>
<body class="flex flex-col min-h-screen">
  {\% include "includes/menu.html" %}

  <main class="flex-auto">
    {\% block content %}{\% endblock content %}
  </main>

  {\% include "includes/footer.html" %}

  <script src="{\% static 'js/main.js' %}"></script>
</body>
</html>
```

Edite `index.html`

```html
<!-- index.html -->
{\% extends "base.html" %}

{\% block content %}
  <h1 class="text-2xl font-bold">Conteúdo</h1>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Vero tenetur repudiandae id animi, labore magni cumque tempore eum culpa esse exercitationem modi est enim sunt in maxime aut quo deleniti!</p>
{\% endblock content %}
```

Edite `menu.html`

```html
<!-- menu.html -->
<header class="menu h-16 bg-slate-800 flex justify-center items-center">
  <h1 class="text-2xl font-bold">Menu</h1>
</header>
```

Edite `footer.html`

```html
<!-- footer.html -->
<footer class="h-16 bg-slate-800 text-gray-100 flex justify-between items-center px-4 text-lg">
  <h1>Dicas de Django © 2023</h1>
  <h1>by Regis do Python</h1>
</footer>
```

Edite `pagination.html`

```html
<!-- pagination.html -->
```

## Criando os arquivos estáticos

```
mkdir -p backend/core/static/{css,js}
touch backend/core/static/css/style.css
touch backend/core/static/js/main.js
```

Edite `style.css`

```css
.menu {
    color: yellow;
}
```

Edite `main.js`

```js
// main.js
console.log('Teste')
```

## Rodando migrate

```
python manage.py migrate
```

## Rodando o projeto

```
python manage.py runserver
```

## Renderizando a página

Edite `core/views.py`

```python
# core/views.py
from django.shortcuts import render


def index(request):
    template_name = 'index.html'
    return render(request, template_name)
```

Edite `core/urls.py`

```python
# core/urls.py
from django.urls import path
from backend.core import views as v


app_name = 'core'


urlpatterns = [
    path('', v.index, name='index'),  # noqa E501
]
```


# Dica 07 - PostgreSQL, pgAdmin e MailHog com docker-compose

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/eOMTjETFCko)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-5018000e722caf4e1878706eaafcebed2174f710%2Fdocker-compose.png?alt=media)

## portainer

Instale o [docker](https://docs.docker.com/get-docker/) e o [docker-compose](https://docs.docker.com/compose/install/) na sua máquina.

```
docker --version
docker-compose --version
```

Vamos usar o [Portainer](https://www.portainer.io/) para monitorar nossos containers.

```
# Portainer
docker run -d \
--name myportainer \
-p 9000:9000 \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/portainer:/data \
portainer/portainer
```

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b9d341e9d43ea9525260a48f7ca73c5d03f42bcd%2Fportainer.png?alt=media)

## docker-compose

Edite `docker-compose.yml`

```yml
version: "3.8"

services:
  db:
    container_name: dicas_de_django_db
    image: postgres:14-alpine
    restart: always
    user: postgres  # importante definir o usuário
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - LC_ALL=C.UTF-8
      - POSTGRES_PASSWORD=postgres  # senha padrão
      - POSTGRES_USER=postgres  # usuário padrão
      - POSTGRES_DB=dicas_de_django_db  # necessário porque foi configurado assim no settings
    ports:
      - 5431:5432  # repare na porta externa 5431
    networks:
      - dicas-de-django-network

  pgadmin:
    container_name: dicas_de_django_pgadmin
    image: dpage/pgadmin4
    restart: unless-stopped
    volumes:
       - pgadmin:/var/lib/pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@admin.com
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: 'False'
    ports:
      - 5051:80
    networks:
      - dicas-de-django-network

  mailhog:
    container_name: dicas_de_django_mailhog
    image: mailhog/mailhog
    restart: always
    logging:
      driver: 'none'
    ports:
      - 1025:1025
      - 8025:8025
    networks:
      - dicas-de-django-network

volumes:
  pgdata:  # mesmo nome do volume externo definido na linha 10
  pgadmin:

networks:
  dicas-de-django-network:
```

Edite `settings.py`

```python
# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config('POSTGRES_DB', 'dicas_de_django_db'),  # postgres
        'USER': config('POSTGRES_USER', 'postgres'),
        'PASSWORD': config('POSTGRES_PASSWORD', 'postgres'),
        # 'db' caso exista um serviço com esse nome.
        # 'HOST': config('DB_HOST', 'db'),
        'HOST': config('DB_HOST', 'localhost'),
        'PORT': config('DB_PORT', 5431, cast=int),
    }
}

# Email config
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
EMAIL_HOST = config('EMAIL_HOST', 'localhost')  # localhost 0.0.0.0
EMAIL_PORT = config('EMAIL_PORT', 1025, cast=int)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
```

Edite `contrib/env_gen.py`

```
DB_PORT=
```

Edite `.env`

```
POSTGRES_DB=dicas_de_django_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DB_HOST=localhost
DB_PORT=5431
```

```
python manage.py migrate
```

```
django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 module: No module named 'psycopg2'
```

```
pip install psycopg2-binary
pip freeze | grep psycopg2 >> requirements.txt
```

```
python manage.py migrate
python manage.py createsuperuser --username="admin" --email=""
python manage.py runserver
```

## pgAdmin

Conectar no pgAdmin

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-eadd17fd84a7b7c472e50f17cf6aeb5dcbf2d78d%2Fpgadmin.png?alt=media)

## Rodando o Django dentro do container

Instale o gunicorn.

```
pip install gunicorn
pip freeze | grep gunicorn >> requirements.txt
```

Edite `Dockerfile`

```
FROM python:3.10-slim

ENV PYTHONUNBUFFERED 1
ENV DJANGO_ENV dev
ENV DOCKER_CONTAINER 1
RUN mkdir /app
WORKDIR /app
EXPOSE 8000

COPY requirements.txt .
RUN pip install -U pip && pip install -r requirements.txt

COPY manage.py .
COPY backend backend

CMD python manage.py collectstatic --no-input
CMD gunicorn backend.wsgi:application -b 0.0.0.0:8000
```

Edite docker-compose.yml

```
version: "3.8"

services:
  ...

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: dicas_de_django_app
    hostname: app
    stdin_open: true
    expose:
      - '8000'
    volumes:
      - .env.docker:/app/.env
    command: bash -c "gunicorn backend.wsgi:application -b 0.0.0.0:8000"
    depends_on:
      - db
    networks:
      - dicas-de-django-network

  nginx:
    container_name: dicas_de_django_nginx
    image: nginx
    hostname: nginx
    ports:
      - '80:8000'
    volumes:
      - ./docker/config/nginx/:/etc/nginx/conf.d/
    depends_on:
      - app
    networks:
      - dicas-de-django-network
```

Edite docker/config/nginx/app.conf

```
# define group app
upstream app {
  # define server app
  server app:8000;
}

# server
server {
  listen 8000;
  charset utf-8;

  client_max_body_size 50M;

  # domain localhost
  server_name localhost;

  # Handle favicon.ico
  location = /favicon.ico {
    return 204;
    access_log off;
    log_not_found off;
  }

  # Django app
  location / {
    proxy_pass http://app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
  }
}
```

Rode o docker novamente.

```
docker-compose up --build -d

docker container exec -it dicas_de_django_app python manage.py migrate
docker container exec -it dicas_de_django_app python manage.py createsuperuser --username="admin" --email=""
```

Edite `.env.docker`

```
POSTGRES_DB=dicas_de_django_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DB_HOST=db
DB_PORT=5432
```

Coloque `.env.docker` no `.gitignore`

```
.env.docker
```

## Servindo arquivos estáticos no Docker com WhiteNoise

Para servir os estáticos no Docker vamos usar o [WhiteNoise](http://whitenoise.evans.io/en/latest/).

```
pip install whitenoise

pip freeze | grep whitenoise >> requirements.txt
```

Edite `settings.py`

```python
# settings.py
MIDDLEWARE = [
    # ...
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]
```

```
docker-compose up --build -d
```


# Dica 08 - Aplicando isort e autopep8

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/A3RRHIJ0514)

O [isort](https://pycqa.github.io/isort/#installing-isort) serve para ordenar os imports.

O [autopep8](https://pypi.org/project/autopep8/) serve para ajustar os arquivos segundo a [PEP8](https://peps.python.org/pep-0008/).

```
pip install isort autopep8
pip freeze | grep -E 'isort|autopep8' >> requirements.txt
```

Para perceber o efeito vamos editar

```
touch lorem.py
```

```python
# lorem.py
import requests
import json
import click


def long_function_name(var_one, var_two, var_three, var_four, var_five, var_six, var_seven, var_eight):
    return
```

Então rode

```
autopep8 --in-place --aggressive --aggressive lorem.py

find backend -name "*.py" | xargs autopep8 --max-line-length 120 --in-place

isort -m 3 *
```

O resultado será:

```python
# lorem.py
import json

import click
import requests


def long_function_name(
        var_one,
        var_two,
        var_three,
        var_four,
        var_five,
        var_six,
        var_seven,
        var_eight):
    return
```


# Dica 09 - Aplicando djhtml

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/23mdIXcUp_o)

[djhtml](https://github.com/rtts/djhtml)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

```
pip install djhtml
pip freeze | grep djhtml >> requirements.txt
```

Antes:

```html
{\% extends "base.html" %}

{\% block content %}
  <h1 class="text-2xl font-bold">Conteúdo</h1>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Vero tenetur repudiandae id animi, labore magni cumque tempore eum culpa esse exercitationem modi est enim sunt in maxime aut quo deleniti!</p>
  {\% if 'a' == 'a' %}
  {\% if '1' == '1' %}
  <span>A1</span>
  {\% endif %}
  {\% endif %}
{\% endblock content %}
```

```
find backend -name "*.html" | xargs djhtml -t 2 -i
```

Depois:

```html
{\% extends "base.html" %}

{\% block content %}
  <h1 class="text-2xl font-bold">Conteúdo</h1>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Vero tenetur repudiandae id animi, labore magni cumque tempore eum culpa esse exercitationem modi est enim sunt in maxime aut quo deleniti!</p>
  {\% if 'a' == 'a' %}
    {\% if '1' == '1' %}
      <span>A1</span>
    {\% endif %}
  {\% endif %}
{\% endblock content %}
```


# Dica 10 - Criando Makefile

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/HdsDqWXNHSc)

```
touch Makefile
```

Edite `Makefile`

```
indenter:
    find backend -name "*.html" | xargs djhtml -t 2 -i

autopep8:
    find backend -name "*.py" | xargs autopep8 --max-line-length 120 --in-place

isort:
    isort -m 3 *

up:
    docker-compose up -d

lint: autopep8 isort indenter

```


# Dica 11 - Criando Landpage de produtos

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/zpC5qH8-iT8)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Com destaque para

```html
<img src="{\% static 'img/products/men/product1.jpg' %}" class="rounded-tl-lg rounded-tr-lg" />
```

```html
<style type="text/tailwindcss">
  @layer utilities {
    .form-control {
      @apply text-gray-600 placeholder-gray-400 px-4 py-3 w-full rounded-lg focus:outline-none mb-4
    }
  }
</style>
```

## Links

<https://github.com/itzpradip/tailwind-eshop-static-html>

<https://youtu.be/iNDvh7O2WZM>


# 072-012-fale-co-nosco-form-email

## Dica 12 - Fale conosco com formulário para enviar mensagem

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/XXSAalrmtxI)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Criar app `crm`

```
cd backend
python ../manage.py startapp crm
cd ..

touch backend/crm/forms.py
touch backend/crm/urls.py
```

Edite `crm/apps.py`

```python
# crm/apps.py
from django.apps import AppConfig


class CrmConfig(AppConfig):
    ...
    name = 'backend.crm'
```

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    # minhas apps
    'backend.core',
    'backend.crm',
]
```

Edite `backend/urls.py`

```python
# backend/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    ...
    path('crm/', include('backend.crm.urls', namespace='crm')),  # noqa E501
    ...
]
```

Edite `crm/urls.py`

```python
# crm/urls.py
from django.urls import path

from backend.crm import views as v

app_name = 'crm'


urlpatterns = [
    path('contact/', v.send_contact, name='send_contact'),  # noqa E501
]
```

Edite `crm/forms.py`

```python
# crm/forms.py
from django import forms


class ContactForm(forms.Form):
    name = forms.CharField('nome', max_length=255)
    email = forms.EmailField('email',)  # sender
    title = forms.CharField('título', max_length=100)  # subject
    body = forms.CharField(widget=forms.Textarea)  # message
```

Edite `crm/views.py`

## crm/views.py

```python
from django.core.mail import send_mail
from django.shortcuts import redirect
from django.views.decorators.http import require_http_methods

from .forms import ContactForm


@require_http_methods(['POST'])
def send_contact(request):
    form = ContactForm(request.POST or None)

    if form.is_valid():
        subject = form.cleaned_data.get('title')
        message = form.cleaned_data.get('body')
        sender = form.cleaned_data.get('email')
        send_mail(
            subject,
            message,
            sender,
            ['localhost'],
            fail_silently=False,
        )
        return redirect('core:index')
```

Edite `index.html`

```html
<form action="{\% url 'crm:send_contact' %}" method="POST">
```


# Dica 13 - Dashboard com Django e Tailwind CSS

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/mTpJx4_rTJQ)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Edite `index.html`

```html
<a href="{\% url 'core:dashboard' %}" class="text-gray-600 hover:text-purple-600 p-4 px-3 sm:px-4">Home</a>
```

Edite `core/urls.py`

```python
# core/urls.py
from django.urls import path

from backend.core import views as v

app_name = 'core'


urlpatterns = [
    path('', v.index, name='index'),  # noqa E501
    path('dashboard/', v.dashboard, name='dashboard'),  # noqa E501
]
```

Edite `core/views.py`

```python
# core/views.py
def dashboard(request):
    template_name = 'dashboard.html'
    return render(request, template_name)
```

Edite `base.html`

```html
<!-- base.html -->
{\% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta name="author" content="Themesberg">
    <meta name="generator" content="Hugo 0.107.0">

    <title>Dicas de Django | Dashboard</title>

    <link rel="canonical" href="https://themesberg.com/product/tailwind-css/dashboard-windster">

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="{\% static 'css/app.css' %}">
    <link rel="apple-touch-icon" sizes="180x180" href="https://demo.themesberg.com/windster/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="https://demo.themesberg.com/windster/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="https://demo.themesberg.com/windster/favicon-16x16.png">
    <link rel="icon" type="image/png" href="https://demo.themesberg.com/windster/favicon.ico">
    <link rel="mask-icon" href="https://demo.themesberg.com/windster/safari-pinned-tab.svg" color="#5bbad5">
    <meta name="msapplication-TileColor" content="#ffffff">
    <meta name="theme-color" content="#ffffff">
    <!-- Twitter -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:site" content="@">
    <meta name="twitter:creator" content="@">
    <meta name="twitter:title" content="Tailwind CSS Dashboard - Windster">
    <meta name="twitter:description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta name="twitter:image" content="https://demo.themesberg.com/windster/">

    <!-- Facebook -->
    <meta property="og:url" content="https://demo.themesberg.com/windster/">
    <meta property="og:title" content="Tailwind CSS Dashboard - Windster">
    <meta property="og:description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta property="og:type" content="website">
    <meta property="og:image" content="https://demo.themesberg.com/docs/images/og-image.jpg">
    <meta property="og:image:type" content="image/png">

  </head>
  <body class="bg-gray-50">

    {\% include "includes/nav.html" %}
    <div class="flex overflow-hidden bg-white pt-16">

      {\% include "includes/aside.html" %}

      <div class="bg-gray-900 opacity-50 hidden fixed inset-0 z-10" id="sidebarBackdrop"></div>

      <div id="main-content" class="h-full w-full bg-gray-50 relative overflow-y-auto lg:ml-64">
        {\% block content %}{\% endblock content %}
        {\% include "includes/footer.html" %}

      </div>

    </div>

    <script async defer src="https://buttons.github.io/buttons.js"></script>
    <script src="{\% static 'js/app.bundle.js' %}"></script>
  </body>
</html>
```

Edite `includes/aside.html`

```html
<!-- includes/aside.html -->
<aside id="sidebar" class="fixed hidden z-20 h-full top-0 left-0 pt-16 flex lg:flex flex-shrink-0 flex-col w-64 transition-width duration-75" aria-label="Sidebar">
  <div class="relative flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-white pt-0">
    <div class="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
      <div class="flex-1 px-3 bg-white divide-y space-y-1">
        <ul class="space-y-2 pb-2">
          <li>
            <form action="#" method="GET" class="lg:hidden">
              <label for="mobile-search" class="sr-only">Search</label>
              <div class="relative">
                <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                  <svg class="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
                </div>
                <input type="text" name="email" id="mobile-search" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-cyan-600 focus:ring-cyan-600 block w-full pl-10 p-2.5" placeholder="Search">
              </div>
            </form>
          </li>
          <li>
            <a href="{\% url 'core:dashboard' %}" class="text-base text-gray-900 font-normal rounded-lg flex items-center p-2 hover:bg-gray-100 group">
              <svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z"></path></svg>
              <span class="ml-3">Dashboard</span>
            </a>
          </li>
          <li>
            <a href="" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group ">
              <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>
              <span class="ml-3 flex-1 whitespace-nowrap">Usuários</span>
            </a>
          </li>
          <li>
            <a href="" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group ">
              <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v1H5a1 1 0 00-.994.89l-1 9A1 1 0 004 18h12a1 1 0 00.994-1.11l-1-9A1 1 0 0015 7h-1V6a4 4 0 00-4-4zm2 5V6a2 2 0 10-4 0v1h4zm-6 3a1 1 0 112 0 1 1 0 01-2 0zm7-1a1 1 0 100 2 1 1 0 000-2z" clip-rule="evenodd"></path></svg>
              <span class="ml-3 flex-1 whitespace-nowrap">Produtos</span>
            </a>
          </li>
          <li>
            <a href="" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group ">
              <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clip-rule="evenodd"></path></svg>
              <span class="ml-3 flex-1 whitespace-nowrap">Login</span>
            </a>
          </li>
          <li>
            <a href="" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group ">
              <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 4a3 3 0 00-3 3v6a3 3 0 003 3h10a3 3 0 003-3V7a3 3 0 00-3-3H5zm-1 9v-1h5v2H5a1 1 0 01-1-1zm7 1h4a1 1 0 001-1v-1h-5v2zm0-4h5V8h-5v2zM9 8H4v2h5V8z" clip-rule="evenodd"></path></svg>
              <span class="ml-3 flex-1 whitespace-nowrap">Cadastre-se</span>
            </a>
          </li>
        </ul>
        <div class="space-y-2 pt-2">
          <a href="https://flowbite.com/docs/getting-started/introduction/" target="_blank" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2">
            <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"></path></svg>
            <span class="ml-3">Documentação</span>
          </a>
          <a href="https://flowbite.com/docs/components/alerts/" target="_blank" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2">
            <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"></path></svg>
            <span class="ml-3">Componentes</span>
          </a>
          <a href="https://github.com/themesberg/windster-tailwind-css-dashboard/issues" target="_blank" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2">
            <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-2 0c0 .993-.241 1.929-.668 2.754l-1.524-1.525a3.997 3.997 0 00.078-2.183l1.562-1.562C15.802 8.249 16 9.1 16 10zm-5.165 3.913l1.58 1.58A5.98 5.98 0 0110 16a5.976 5.976 0 01-2.516-.552l1.562-1.562a4.006 4.006 0 001.789.027zm-4.677-2.796a4.002 4.002 0 01-.041-2.08l-.08.08-1.53-1.533A5.98 5.98 0 004 10c0 .954.223 1.856.619 2.657l1.54-1.54zm1.088-6.45A5.974 5.974 0 0110 4c.954 0 1.856.223 2.657.619l-1.54 1.54a4.002 4.002 0 00-2.346.033L7.246 4.668zM12 10a2 2 0 11-4 0 2 2 0 014 0z" clip-rule="evenodd"></path></svg>
            <span class="ml-3">Ajuda</span>
          </a>
        </div>
      </div>
    </div>
  </div>
</aside>
```

Edite `includes/nav.html`

```html
<!-- includes/nav.html -->
<nav class="bg-white border-b border-gray-200 fixed z-30 w-full">
  <div class="px-3 py-3 lg:px-5 lg:pl-3">
    <div class="flex items-center justify-between">
      <div class="flex items-center justify-start">
        <button id="toggleSidebarMobile" aria-expanded="true" aria-controls="sidebar" class="lg:hidden mr-2 text-gray-600 hover:text-gray-900 cursor-pointer p-2 hover:bg-gray-100 focus:bg-gray-100 focus:ring-2 focus:ring-gray-100 rounded">
          <svg id="toggleSidebarMobileHamburger" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h6a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
          <svg id="toggleSidebarMobileClose" class="w-6 h-6 hidden" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
        </button>
        <a href="https://demo.themesberg.com/windster/" class="text-xl font-bold flex items-center lg:ml-2.5">
          <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-6 mr-2" alt="Windster Logo">
          <span class="self-center whitespace-nowrap">Dicas de Django</span>
        </a>
        <form action="#" method="GET" class="hidden lg:block lg:pl-32">
          <label for="topbar-search" class="sr-only">Search</label>
          <div class="mt-1 relative lg:w-64">
            <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
              <svg class="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path></svg>
            </div>
            <input type="text" name="email" id="topbar-search" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full pl-10 p-2.5" placeholder="Search">
          </div>
        </form>
      </div>
      <div class="flex items-center">

        <button id="toggleSidebarMobileSearch" type="button" class="lg:hidden text-gray-500 hover:text-gray-900 hover:bg-gray-100 p-2 rounded-lg">
          <span class="sr-only">Search</span>

          <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path></svg>
        </button>
        <div class="hidden lg:flex items-center">
          <span class="text-base font-normal text-gray-500 mr-5">Open source ❤️</span>
          <div class="-mb-1">
            <a class="github-button" href="https://github.com/themesberg/windster-tailwind-css-dashboard" data-color-scheme="no-preference: dark; light: light; dark: light;" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star themesberg/windster-tailwind-css-dashboard on GitHub">Star</a>
          </div>
        </div>
      </div>
    </div>
  </div>
</nav>
```

Edite `includes/footer.html`

```html
<!-- includes/footer.html -->
<footer class="bg-white md:flex md:items-center md:justify-between shadow rounded-lg p-4 md:p-6 xl:p-8 my-6 mx-4">
  <ul class="flex items-center flex-wrap mb-6 md:mb-0">
    <li><a href="#" class="text-sm font-normal text-gray-500 hover:underline mr-4 md:mr-6">Terms and conditions</a></li>
    <li><a href="#" class="text-sm font-normal text-gray-500 hover:underline mr-4 md:mr-6">Privacy Policy</a></li>
    <li><a href="#" class="text-sm font-normal text-gray-500 hover:underline mr-4 md:mr-6">Licensing</a></li>
    <li><a href="#" class="text-sm font-normal text-gray-500 hover:underline mr-4 md:mr-6">Cookie Policy</a></li>
    <li><a href="{\% url 'core:index' %}#contato" class="text-sm font-normal text-gray-500 hover:underline">Contact</a></li>
  </ul>
  <div class="flex sm:justify-center space-x-6">
    <a href="#" class="text-gray-500 hover:text-gray-900">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
        <path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd" />
      </svg>
    </a>
    <a href="#" class="text-gray-500 hover:text-gray-900">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
        <path fill-rule="evenodd" d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z" clip-rule="evenodd" />
      </svg>
    </a>
    <a href="#" class="text-gray-500 hover:text-gray-900">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
        <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
      </svg>
    </a>
    <a href="https://github.com/rg3915/dicas-de-django" target="_blank" class="text-gray-500 hover:text-gray-900">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
        <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
      </svg>
    </a>
    <a href="#" class="text-gray-500 hover:text-gray-900">
      <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
        <path fill-rule="evenodd" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z" clip-rule="evenodd" />
      </svg>
    </a>
  </div>
</footer>
<p class="text-center text-sm text-gray-500 my-10">
  Dicas de Django &copy; 2023 <a href="https://themesberg.com" class="hover:underline" target="_blank">Themesberg</a>. Regis do Python.
</p>
```

Edite `dashboard.html`

```html
<!-- dashboard.html -->
{\% extends "base.html" %}

{\% block content %}
  <main>

    <div class="pt-6 px-4">
      <div class="w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4">
        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8  2xl:col-span-2">

          <div class="flex items-center justify-between mb-4">
            <div class="flex-shrink-0">
              <span class="text-2xl sm:text-3xl leading-none font-bold text-gray-900">$45,385</span>
              <h3 class="text-base font-normal text-gray-500">Sales this week</h3>
            </div>
            <div class="flex items-center justify-end flex-1 text-green-500 text-base font-bold">
              12.5%
              <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                <path fill-rule="evenodd"
                  d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
                  clip-rule="evenodd"></path>
              </svg>
            </div>
          </div>
          <div id="main-chart"></div>

        </div>

        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">

          <!-- Card Title -->
          <div class="mb-4 flex items-center justify-between">
            <div>
              <h3 class="text-xl font-bold text-gray-900 mb-2">Latest Transactions</h3>
              <span class="text-base font-normal text-gray-500">This is a list of latest transactions</span>
            </div>
            <div class="flex-shrink-0">
              <a href="#" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2">View all</a>
            </div>
          </div>
          <!-- Table -->
          <div class="flex flex-col mt-8">
            <div class="overflow-x-auto rounded-lg">
              <div class="align-middle inline-block min-w-full">
                <div class="shadow overflow-hidden sm:rounded-lg">
                  <table class="min-w-full divide-y divide-gray-200">
                    <thead class="bg-gray-50">
                      <tr>
                        <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                          Transaction
                        </th>
                        <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                          Date & Time
                        </th>
                        <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                          Amount
                        </th>
                      </tr>
                    </thead>
                    <tbody class="bg-white">
                      <tr>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900">
                          Payment from <span class="font-semibold">Bonnie Green</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 23 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $2300
                        </td>
                      </tr>
                      <tr class="bg-gray-50">
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900 rounded-lg rounded-left">
                          Payment refund to <span class="font-semibold">#00910</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 23 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          -$670
                        </td>
                      </tr>
                      <tr>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900">
                          Payment failed from <span class="font-semibold">#087651</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 18 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $234
                        </td>
                      </tr>
                      <tr class="bg-gray-50">
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900 rounded-lg rounded-left">
                          Payment from <span class="font-semibold">Lana Byrd</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 15 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $5000
                        </td>
                      </tr>
                      <tr>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900">
                          Payment from <span class="font-semibold">Jese Leos</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 15 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $2300
                        </td>
                      </tr>
                      <tr class="bg-gray-50">
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900 rounded-lg rounded-left">
                          Payment from <span class="font-semibold">THEMESBERG LLC</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 11 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $560
                        </td>
                      </tr>

                      <tr>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-900">
                          Payment from <span class="font-semibold">Lana Lysle</span>
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
                          Apr 6 ,2021
                        </td>
                        <td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
                          $1437
                        </td>
                      </tr>
                    </tbody>
                  </table>
                </div>
              </div>
            </div>
          </div>

        </div>

      </div>

      <div class="mt-4 w-full grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">

          <div class="flex items-center">
            <div class="flex-shrink-0">
              <span class="text-2xl sm:text-3xl leading-none font-bold text-gray-900">2,340</span>
              <h3 class="text-base font-normal text-gray-500">New products this week</h3>
            </div>
            <div class="ml-5 w-0 flex items-center justify-end flex-1 text-green-500 text-base font-bold">
              14.6%
              <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                <path fill-rule="evenodd"
                  d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
                  clip-rule="evenodd"></path>
              </svg>
            </div>
          </div>

        </div>

        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">

          <div class="flex items-center">
            <div class="flex-shrink-0">
              <span class="text-2xl sm:text-3xl leading-none font-bold text-gray-900">5,355</span>
              <h3 class="text-base font-normal text-gray-500">Visitors this week</h3>
            </div>
            <div class="ml-5 w-0 flex items-center justify-end flex-1 text-green-500 text-base font-bold">
              32.9%
              <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                <path fill-rule="evenodd"
                  d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
                  clip-rule="evenodd"></path>
              </svg>
            </div>
          </div>

        </div>

        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">

          <div class="flex items-center">
            <div class="flex-shrink-0">
              <span class="text-2xl sm:text-3xl leading-none font-bold text-gray-900">385</span>
              <h3 class="text-base font-normal text-gray-500">User signups this week</h3>
            </div>
            <div class="ml-5 w-0 flex items-center justify-end flex-1 text-red-500 text-base font-bold">
              -2.7%
              <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                <path fill-rule="evenodd"
                  d="M14.707 12.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l2.293-2.293a1 1 0 011.414 0z"
                  clip-rule="evenodd"></path>
              </svg>
            </div>
          </div>

        </div>

      </div>
      <div class="grid grid-cols-1 2xl:grid-cols-2 xl:gap-4 my-4">
        <!-- Top Sales Card -->
        <div class="bg-white shadow rounded-lg mb-4 p-4 sm:p-6 h-full">
          <div class="flex items-center justify-between mb-4">
            <h3 class="text-xl font-bold leading-none text-gray-900">Latest Customers</h3>
            <a href="#" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
              View all
            </a>
          </div>
          <div class="flow-root">
            <ul role="list" class="divide-y divide-gray-200">
              <li class="py-3 sm:py-4">
                <div class="flex items-center space-x-4">
                  <div class="flex-shrink-0">
                    <img class="h-8 w-8 rounded-full" src="https://demo.themesberg.com/windster/images/users/neil-sims.png" alt="Neil image">
                  </div>
                  <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium text-gray-900 truncate">
                      Neil Sims
                    </p>
                    <p class="text-sm text-gray-500 truncate">
                      email@windster.com
                    </p>
                  </div>
                  <div class="inline-flex items-center text-base font-semibold text-gray-900">
                    $320
                  </div>
                </div>
              </li>
              <li class="py-3 sm:py-4">
                <div class="flex items-center space-x-4">
                  <div class="flex-shrink-0">
                    <img class="h-8 w-8 rounded-full" src="https://demo.themesberg.com/windster/images/users/bonnie-green.png" alt="Neil image">
                  </div>
                  <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium text-gray-900 truncate">
                      Bonnie Green
                    </p>
                    <p class="text-sm text-gray-500 truncate">
                      email@windster.com
                    </p>
                  </div>
                  <div class="inline-flex items-center text-base font-semibold text-gray-900">
                    $3467
                  </div>
                </div>
              </li>
              <li class="py-3 sm:py-4">
                <div class="flex items-center space-x-4">
                  <div class="flex-shrink-0">
                    <img class="h-8 w-8 rounded-full" src="https://demo.themesberg.com/windster/images/users/michael-gough.png" alt="Neil image">
                  </div>
                  <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium text-gray-900 truncate">
                      Michael Gough
                    </p>
                    <p class="text-sm text-gray-500 truncate">
                      email@windster.com
                    </p>
                  </div>
                  <div class="inline-flex items-center text-base font-semibold text-gray-900">
                    $67
                  </div>
                </div>
              </li>
              <li class="py-3 sm:py-4">
                <div class="flex items-center space-x-4">
                  <div class="flex-shrink-0">
                    <img class="h-8 w-8 rounded-full" src="https://demo.themesberg.com/windster/images/users/thomas-lean.png" alt="Neil image">
                  </div>
                  <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium text-gray-900 truncate">
                      Thomes Lean
                    </p>
                    <p class="text-sm text-gray-500 truncate">
                      email@windster.com
                    </p>
                  </div>
                  <div class="inline-flex items-center text-base font-semibold text-gray-900">
                    $2367
                  </div>
                </div>
              </li>
              <li class="pt-3 sm:pt-4 pb-0">
                <div class="flex items-center space-x-4">
                  <div class="flex-shrink-0">
                    <img class="h-8 w-8 rounded-full" src="https://demo.themesberg.com/windster/images/users/lana-byrd.png" alt="Neil image">
                  </div>
                  <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium text-gray-900 truncate">
                      Lana Byrd
                    </p>
                    <p class="text-sm text-gray-500 truncate">
                      email@windster.com
                    </p>
                  </div>
                  <div class="inline-flex items-center text-base font-semibold text-gray-900">
                    $367
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
        <!-- Sessions by device Card -->
        <div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 ">

          <!-- Card Title -->
          <h3 class="text-xl leading-none font-bold text-gray-900 mb-10">Acquisition Overview</h3>
          <div class="block w-full overflow-x-auto">
            <table class="items-center w-full bg-transparent border-collapse">
              <thead>
                <tr>
                  <th class="px-4 bg-gray-50 text-gray-700 align-middle py-3 text-xs font-semibold text-left uppercase border-l-0 border-r-0 whitespace-nowrap">Top Channels</th>
                  <th class="px-4 bg-gray-50 text-gray-700 align-middle py-3 text-xs font-semibold text-left uppercase border-l-0 border-r-0 whitespace-nowrap">Users</th>
                  <th class="px-4 bg-gray-50 text-gray-700 align-middle py-3 text-xs font-semibold text-left uppercase border-l-0 border-r-0 whitespace-nowrap min-w-140-px"></th>
                </tr>
              </thead>
              <tbody class="divide-y divide-gray-100">
                <tr class="text-gray-500">
                  <th class="border-t-0 px-4 align-middle text-sm font-normal whitespace-nowrap p-4 text-left">Organic Search</th>
                  <td class="border-t-0 px-4 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4">5,649</td>
                  <td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">30%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-cyan-600 h-2 rounded-sm" style="width: 30%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="text-gray-500">
                  <th class="border-t-0 px-4 align-middle text-sm font-normal whitespace-nowrap p-4 text-left">Referral</th>
                  <td class="border-t-0 px-4 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4">4,025</td>
                  <td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">24%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-orange-300 h-2 rounded-sm" style="width: 24%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="text-gray-500">
                  <th class="border-t-0 px-4 align-middle text-sm font-normal whitespace-nowrap p-4 text-left">Direct</th>
                  <td class="border-t-0 px-4 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4">3,105</td>
                  <td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">18%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-teal-400 h-2 rounded-sm" style="width: 18%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="text-gray-500">
                  <th class="border-t-0 px-4 align-middle text-sm font-normal whitespace-nowrap p-4 text-left">Social</th>
                  <td class="border-t-0 px-4 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4">1251</td>
                  <td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">12%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-pink-600 h-2 rounded-sm" style="width: 12%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="text-gray-500">
                  <th class="border-t-0 px-4 align-middle text-sm font-normal whitespace-nowrap p-4 text-left">Other</th>
                  <td class="border-t-0 px-4 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4">734</td>
                  <td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">9%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-indigo-600 h-2 rounded-sm" style="width: 9%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="text-gray-500">
                  <th class="border-t-0 align-middle text-sm font-normal whitespace-nowrap p-4 pb-0 text-left">Email</th>
                  <td class="border-t-0 align-middle text-xs font-medium text-gray-900 whitespace-nowrap p-4 pb-0">456</td>
                  <td class="border-t-0 align-middle text-xs whitespace-nowrap p-4 pb-0">
                    <div class="flex items-center">
                      <span class="mr-2 text-xs font-medium">7%</span>
                      <div class="relative w-full">
                        <div class="w-full bg-gray-200 rounded-sm h-2">
                          <div class="bg-purple-500 h-2 rounded-sm" style="width: 7%"></div>
                        </div>
                      </div>
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>

        </div>

      </div>
    </div>
  </main>
{\% endblock content %}
```

Baixar

[backend/core/static/css/app.css](https://github.com/rg3915/dicas-de-django/blob/main/backend/core/static/css/app.css)

[backend/core/static/css/app.bundle.js](https://github.com/rg3915/dicas-de-django-2023/blob/main/backend/core/static/js/app.bundle.js)

## Links

<https://demo.themesberg.com/windster/>

<https://github.com/themesberg/tailwind-dashboard-windster>


# Dica 14 - Django Custom User com e-mail

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/8Hg9ALsxz4c)

User

<https://github.com/django/django/blob/70c945d6b31b41b320e57088702077864428fdc0/django/contrib/auth/models.py#L405>

AbstractUser

<https://github.com/django/django/blob/70c945d6b31b41b320e57088702077864428fdc0/django/contrib/auth/models.py#L334>

AbstractBaseUser

<https://github.com/django/django/blob/70c945d6b31b41b320e57088702077864428fdc0/django/contrib/auth/base_user.py#L56>

Extending User Model Using a Custom Model Extending AbstractBaseUser

<https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractbaseuser>

Django autenticação e login com email - Django login email

<https://youtu.be/dXdMD3LBUvA>

Crie a app `accounts`

```
cd backend
python ../manage.py startapp accounts
cd ..
```

Edite `settings.py`

```python
# settings.py

AUTH_USER_MODEL = 'accounts.User'

INSTALLED_APPS = [
    'backend.accounts',  # <<<
    'django.contrib.admin',
    ...
]
```

Edite `accounts/apps.py`

```python
# accounts/apps.py
from django.apps import AppConfig


class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'backend.accounts'
```

Edite `accounts/models.py`

```python
# accounts/models.py
from __future__ import unicode_literals

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.core.mail import send_mail
from django.db import models
from django.utils.translation import gettext_lazy as _

from .managers import UserManager


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    is_active = models.BooleanField(_('active'), default=True)
    is_admin = models.BooleanField(
        _('admin status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'),
    )

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    def get_full_name(self):
        '''
        Returns the first_name plus the last_name, with a space in between.
        '''
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        '''
        Returns the short name for the user.
        '''
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        '''
        Sends an email to this User.
        '''
        send_mail(subject, message, from_email, [self.email], **kwargs)

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

```

Edite `accounts/admin.py`

```python
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext_lazy as _

from .models import User


class UserAdmin(BaseUserAdmin):
    # The fields to be used in displaying the User model.
    # These override the definitions on the base UserAdmin
    # that reference specific fields on auth.User.
    list_display = ('email', 'first_name', 'last_name', 'is_admin', 'is_active')  # noqa E501
    list_filter = ('is_admin',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {
            'fields': (
                'is_active',
                'is_admin',
                # 'is_superuser',
                'groups',
                'user_permissions',
            )
        }),
        (_('Important dates'), {'fields': ('last_login',)}),
    )
    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
    # overrides get_fieldsets to use this attribute when creating a user.
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)
    filter_horizontal = ('groups', 'user_permissions',)


admin.site.register(User, UserAdmin)

```

Edite `accounts/managers.py`

```python
# accounts/managers.py
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_admin', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_admin') is not True:
            raise ValueError('Superuser must have is_admin=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)

```

Edite `accounts/tests.py`

```python
# accounts/tests.py
from django.test import TestCase

from backend.accounts.models import User


class TestUser(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            email='admin@email.com',
            password='demodemo',
            first_name='Admin',
            last_name='Admin',
        )
        self.superuser = User.objects.create_superuser(
            email='superadmin@email.com',
            password='demodemo'
        )

    def test_user_exists(self):
        self.assertTrue(self.user)

    def test_str(self):
        self.assertEqual(self.user.email, 'admin@email.com')

    def test_return_attributes(self):
        fields = (
            'id',
            'email',
            'first_name',
            'last_name',
            'password',
            'is_active',
            'is_admin',
            'is_superuser',
            'date_joined',
            'last_login',
        )

        for field in fields:
            with self.subTest():
                self.assertTrue(hasattr(User, field))

    def test_user_is_authenticated(self):
        self.assertTrue(self.user.is_authenticated)

    def test_user_is_active(self):
        self.assertTrue(self.user.is_active)

    def test_user_is_staff(self):
        self.assertFalse(self.user.is_staff)

    def test_user_is_superuser(self):
        self.assertFalse(self.user.is_superuser)

    def test_superuser_is_superuser(self):
        self.assertTrue(self.superuser.is_superuser)

    def test_user_has_perm(self):
        self.assertTrue(self.user.has_perm)

    def test_user_has_module_perms(self):
        self.assertTrue(self.user.has_module_perms)

    def test_user_get_full_name(self):
        self.assertEqual(self.user.get_full_name(), 'Admin Admin')

    def test_user_get_short_name(self):
        self.assertEqual(self.user.get_short_name(), 'Admin')

```

```
python manage.py makemigrations
python manage.py migrate
python manage.py test
```

Erro

```
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database 'default'.
```

Delete o banco de dados e recrie novamente.

```
docker container rm dicas_de_django_db -f
docker volume prune -f

python manage.py migrate
python manage.py createsuperuser --email="admin@email.com"

python manage.py test
```


# Dica 15 - Login com e-mail no Django

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/5F4uQeTmOLg)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

```
mkdir -p backend/accounts/templates/registration
touch backend/accounts/templates/registration/login.html
```

Edite `index.html`

```html
<a href="{\% url 'login' %}" class="text-gray-600 hover:text-purple-600 p-4 px-3 sm:px-4">Login</a>
```

Edite `settings.py`

```python
# settings.py

LOGIN_REDIRECT_URL = 'core:dashboard'
LOGOUT_REDIRECT_URL = 'core:index'
```

Edite `backend/urls.py`

```python
# backend/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('', include('backend.core.urls', namespace='core')),  # noqa E501
    path('accounts/', include('backend.accounts.urls')),  # noqa E501
    ...
]
```

Edite `accounts/urls.py`

```python
# accounts/urls.py
from django.contrib.auth.views import LoginView
from django.urls import path


urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),  # noqa E501
]

```

Edite `backend/core/templates/base_login.html`

```html
<!-- base_login.html -->
{\% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta name="author" content="Themesberg">
    <meta name="generator" content="Hugo 0.107.0">

    <title>Dicas de Django | Login</title>

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="{\% static 'css/app.css' %}">
    <link rel="apple-touch-icon" sizes="180x180" href="https://demo.themesberg.com/windster/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="https://demo.themesberg.com/windster/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="https://demo.themesberg.com/windster/favicon-16x16.png">
    <link rel="icon" type="image/png" href="https://demo.themesberg.com/windster/favicon.ico">
    <link rel="mask-icon" href="https://demo.themesberg.com/windster/safari-pinned-tab.svg" color="#5bbad5">
    <meta name="msapplication-TileColor" content="#ffffff">
    <meta name="theme-color" content="#ffffff">
    <!-- Twitter -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:site" content="@">
    <meta name="twitter:creator" content="@">
    <meta name="twitter:title" content="Tailwind CSS Login - Windster">
    <meta name="twitter:description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta name="twitter:image" content="https://demo.themesberg.com/windster/">

    <!-- Facebook -->
    <meta property="og:url" content="https://demo.themesberg.com/windster/">
    <meta property="og:title" content="Tailwind CSS Login - Windster">
    <meta property="og:description" content="Get started with a free and open source Tailwind CSS admin dashboard featuring a sidebar layout, advanced charts, and hundreds of components based on Flowbite">
    <meta property="og:type" content="website">
    <meta property="og:image" content="https://demo.themesberg.com/docs/images/og-image.jpg">
    <meta property="og:image:type" content="image/png">

  </head>
  <body class="bg-gray-50">

    {\% block content %}{\% endblock content %}

    <script async defer src="https://buttons.github.io/buttons.js"></script>
    <script src="{\% static 'js/app.bundle.js' %}"></script>
  </body>
</html>
```

Edite `backend/accounts/templates/registration/login.html`

```html
<!-- login.html -->
<!-- login.html -->
{\% extends "base_login.html" %}

{\% block content %}
  <main class="bg-gray-50">
    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Login
          </h2>
          <form class="mt-8 space-y-6" action="." method="POST">
            {\% csrf_token %}
            <div>
              <label for="id_email" class="text-sm font-medium text-gray-900 block mb-2">E-mail</label>
              <input
                id="id_email"
                type="email"
                name="username"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="nome@example.com"
                required
                autofocus
              >
            </div>
            <div>
              <label for="id_password" class="text-sm font-medium text-gray-900 block mb-2">Senha</label>
              <input
                id="id_password"
                type="password"
                name="password"
                placeholder="••••••••"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                required
              >
            </div>
            <div class="flex items-start">
              <!-- <div class="flex items-center h-5">
                <input id="remember" aria-describedby="remember" name="remember" type="checkbox" class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded">
              </div>
              <div class="text-sm ml-3">
                <label for="remember" class="font-medium text-gray-900">Remember me</label>
              </div> -->
              <a href="" class="text-sm text-teal-500 hover:underline ml-auto">Esqueceu a senha?</a>
            </div>
            <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">Login</button>
            <div class="text-sm font-medium text-gray-500">
              Não cadastrado? <a href="." class="text-teal-500 hover:underline">Criar conta</a>
            </div>
          </form>
        </div>
      </div>
    </div>

  </main>
{\% endblock content %}
```

Edite core/views.py

```python
# core/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render


@login_required
def dashboard(request):
    template_name = 'dashboard.html'
    return render(request, template_name)
```


# Dica 16 - Logout

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/SPnFqVRAows)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Edite `accounts/urls.py`

```python
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),  # noqa E501
    path('logout/', LogoutView.as_view(), name='logout'),  # noqa E501
]
```

Edite `core/templates/includes/nav.html`

```html
<span class="text-base font-normal text-gray-500 mr-5">Olá {{ request.user.first_name }}</span>
<a href="{\% url 'logout' %}" class="text-base font-normal text-gray-500 ml-5">Sair</a>
```


# Dica 17 - Cadastro de Usuários no Django

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/JKUYdjIfugU)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Vamos editar os arquivos:

* accounts/templates/registration/registration\_form.html
* accounts/templates/registration/password\_reset\_confirm.html
* accounts/templates/registration/password\_reset\_complete.html
* accounts/forms.py
* accounts/services.py
* accounts/tokens.py
* accounts/urls.py
* accounts/views.py

<https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.views.PasswordResetConfirmView>

<https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.views.PasswordResetCompleteView>

Edite `login.html`

```html
<a href="{\% url 'signup' %}" class="text-teal-500 hover:underline">Criar conta</a>
```

```
touch backend/accounts/templates/registration/registration_form.html
```

```html
<!-- registration_form.html -->
{\% extends "base_login.html" %}

{\% block content %}
  <main class="bg-gray-50">

    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Crie sua conta
          </h2>
          <form class="mt-8 space-y-6" action="." method="POST">
            {\% csrf_token %}
            <div>
              <label for="id_first_name" class="text-sm font-medium text-gray-900 block mb-2">Nome</label>
              <input
                id="id_first_name"
                type="text"
                name="first_name"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="Primeiro Nome"
                required
              >
            </div>
            <div>
              <label for="id_last_name" class="text-sm font-medium text-gray-900 block mb-2">Sobrenome</label>
              <input
                id="id_last_name"
                type="text"
                name="last_name"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="Sobrenome"
                required
              >
            </div>
            <div>
              <label for="id_email" class="text-sm font-medium text-gray-900 block mb-2">E-mail</label>
              <input
                id="id_email"
                type="email"
                name="email"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="nome@example.com"
                required
              >
            </div>
            <!-- <div>
              <label for="id_password1" class="text-sm font-medium text-gray-900 block mb-2">Senha</label>
              <input
                id="id_password1"
                type="password"
                name="password1"
                placeholder="••••••••"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                required
              >
            </div>
            <div>
              <label for="id_password2" class="text-sm font-medium text-gray-900 block mb-2">Confirmação da Senha</label>
              <input
                id="id_password2"
                type="password"
                name="password2"
                placeholder="••••••••"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                required
              >
            </div> -->
            <div class="flex items-start">
              <div class="flex items-center h-5">
                <input
                  id="remember"
                  type="checkbox"
                  name="remember"
                  aria-describedby="remember"
                  class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded"
                >
              </div>
              <div class="text-sm ml-3">
                <label for="remember" class="font-medium text-gray-900">Eu aceito os <a href="#" class="text-teal-500 hover:underline">Termos e Condições</a></label>
              </div>
            </div>
            <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">Salvar</button>
            <div class="text-sm font-medium text-gray-500">
              Você já tem uma conta? <a href="{\% url 'login' %}" class="text-teal-500 hover:underline">Login</a>
            </div>
          </form>
        </div>
      </div>
    </div>
  </main>

{\% endblock content %}
```

Edite `accounts/forms.py`

```python
# accounts/forms.py
from django import forms

from backend.accounts.models import User


class CustomUserForm(forms.ModelForm):
    first_name = forms.CharField(
        label='Nome',
        max_length=150,
    )
    last_name = forms.CharField(
        label='Sobrenome',
        max_length=150,
    )
    email = forms.EmailField(
        label='E-mail',
    )

    class Meta:
        model = User
        fields = (
            'first_name',
            'last_name',
            'email',
        )

```

Edite `accounts/services.py`

```python
# accounts/services.py
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from .tokens import account_activation_token


def send_mail_to_user(request, user):
    current_site = get_current_site(request)
    use_https = request.is_secure()
    subject = 'Ative sua conta.'
    message = render_to_string('email/account_activation_email.html', {
        'user': user,
        'protocol': 'https' if use_https else 'http',
        'domain': current_site.domain,
        'uid': urlsafe_base64_encode(force_bytes(user.pk)),
        'token': account_activation_token.make_token(user),
    })
    user.email_user(subject, message)
```

Edite `accounts/tokens.py`

```python
# accounts/tokens.py
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from six import text_type


class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            text_type(user.pk) + text_type(timestamp) + text_type(user.email)
        )


account_activation_token = AccountActivationTokenGenerator()
```

Edite `accounts/urls.py`

```python
# accounts/urls.py
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path

from backend.accounts import views as v

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),  # noqa E501
    path('logout/', LogoutView.as_view(), name='logout'),  # noqa E501
    path('register/', v.signup, name='signup'),  # noqa E501
    path('reset/<uidb64>/<token>/', v.MyPasswordResetConfirm.as_view(), name='password_reset_confirm'),  # noqa E501
    path('reset/done/', v.MyPasswordResetComplete.as_view(), name='password_reset_complete'),  # noqa E501
]
```

Instale [django-widget-tweaks](https://pypi.org/project/django-widget-tweaks/).

```
pip install django-widget-tweaks
pip freeze | grep django-widget-tweaks >> requirements.txt
```

Em `settings.py`

```python
INSTALLED_APPS = [
    ...
    # apps de terceiros
    'widget_tweaks',
]
```

Edite `accounts/templates/registration/password_reset_confirm.html`

```html
<!-- password_reset_confirm.html -->
{\% extends "base_login.html" %}
{\% load widget_tweaks %}

{\% block content %}
  <main class="bg-gray-50">
    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Trocar senha
          </h2>
          <p class="text-sm font-medium text-gray-500">Digite sua nova senha.</p>
          {\% if validlink %}
            <form class="mt-8 space-y-6" action="." method="POST">
              {\% csrf_token %}
              {\% for field in form.visible_fields %}
                <div>
                  <label class="text-sm font-medium text-gray-900 block mb-2">{{ field.label }}</label>
                  {\% render_field field class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5" %}
                </div>
                <span class="text-sm font-medium text-gray-500">{{ field.help_text }}</span>
                {\% for error in field.errors %}
                  <span class="text-red-500">{{ error }}</span> <br>
                {\% endfor %}
              {\% endfor %}
              <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">Trocar senha</button>
              <div class="text-sm font-medium text-gray-500">
                Você já tem uma conta? <a href="{\% url 'login' %}" class="text-teal-500 hover:underline">Login</a>
              </div>
            </form>
          {\% else %}
            <p>O link para a recuperação de senha era inválido, possivelmente porque já foi utilizado. Por favor, solicite uma nova recuperação de senha.</p>
          {\% endif %}
        </div>
      </div>
    </div>
  </main>
{\% endblock content %}

```

Edite `accounts/templates/registration/password_reset_complete.html`

```html
<!-- password_reset_complete.html -->
{\% extends "base_login.html" %}

{\% block content %}
  <main class="bg-gray-50">
    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Deu tudo certo!
          </h2>
          <p class="text-sm font-medium text-gray-500">
            Sua senha foi definida. Você pode prosseguir e se <a class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg" href="{\% url 'login' %}">autenticar</a> agora.
          </p>
        </div>
      </div>
    </div>
  </main>
{\% endblock content %}

```

Edite `accounts/views.py`

```python
# accounts/views.py
from django.contrib.auth.views import (
    PasswordResetCompleteView,
    PasswordResetConfirmView
)
from django.shortcuts import redirect, render

from backend.accounts.services import send_mail_to_user

from .forms import CustomUserForm


def signup(request):
    '''
    Cadastra Usuário.
    '''
    template_name = 'registration/registration_form.html'
    form = CustomUserForm(request.POST or None)

    if request.method == 'POST':
        if form.is_valid():
            user = form.save()
            send_mail_to_user(request=request, user=user)
            return redirect('login')

    return render(request, template_name)


class MyPasswordResetConfirm(PasswordResetConfirmView):
    '''
    Requer password_reset_confirm.html
    '''

    def form_valid(self, form):
        self.user.is_active = True
        self.user.save()
        return super(MyPasswordResetConfirm, self).form_valid(form)


class MyPasswordResetComplete(PasswordResetCompleteView):
    '''
    Requer password_reset_complete.html
    '''
    ...

```


# Dica 18 - Esqueci a senha

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/_mYTRnPD8PQ)

<https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.views.PasswordResetView>

<https://docs.djangoproject.com/en/4.1/topics/auth/default/#django.contrib.auth.views.PasswordResetDoneView>

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Edite `login.html`

```html
<a href="{\% url 'password_reset' %}" class="text-sm text-teal-500 hover:underline ml-auto">Esqueceu a senha?</a>
```

Edite `accounts/urls.py`

```python
# accounts/urls.py
urlpatterns = [
    ...
    path('password_reset/', v.MyPasswordReset.as_view(), name='password_reset'),  # noqa E501
    path('password_reset/done/', v.MyPasswordResetDone.as_view(), name='password_reset_done'),  # noqa E501
]
```

Edite `accounts/views.py`

```python
# accounts/views.py
class MyPasswordReset(PasswordResetView):
    '''
    Requer
    registration/password_reset_form.html
    registration/password_reset_email.html
    registration/password_reset_subject.txt  Opcional
    '''
    ...


class MyPasswordResetDone(PasswordResetDoneView):
    '''
    Requer
    registration/password_reset_done.html
    '''
    ...
```

Edite `registration/password_reset_form.html`

```html
<!-- password_reset_form.html -->
{\% extends "base_login.html" %}

{\% block content %}
  <main class="bg-gray-50">

    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Redefinição de senha
          </h2>
          <p class="text-sm font-medium text-gray-500">
            Esqueceu sua senha? Informe seu e-mail abaixo, e nós te enviaremos um e-mail com instruções para configurar uma nova.
          </p>
          <form class="mt-8 space-y-6" action="." method="POST">
            {\% csrf_token %}
            <div>
              <label for="id_email" class="text-sm font-medium text-gray-900 block mb-2">E-mail</label>
              <input
                id="id_email"
                type="email"
                name="email"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="nome@example.com"
                required
              >
            </div>
            <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">Enviar</button>
            <div class="text-sm font-medium text-gray-500">
              Não cadastrado? <a href="{\% url 'signup' %}" class="text-teal-500 hover:underline">Criar conta</a>
            </div>
          </form>
        </div>
      </div>
    </div>
  </main>

{\% endblock content %}
```

Edite `registration/password_reset_email.html`

```html
{\% autoescape off %}
  Para iniciar o processo de redefinição de senha para sua conta {{ user.get_username }}, clique no link abaixo:

  {{ protocol }}://{{ domain }}{\% url 'password_reset_confirm' uidb64=uid token=token %}

  Se clicar no link acima não funcionar, por favor copie e cole a URL no navegador.

  Atenciosamente,
  Equipe Dev.
{\% endautoescape %}
```

Edite `registration/password_reset_done.html`

```html
<!-- password_reset_done.html -->
{\% extends "base_login.html" %}

{\% block content %}
  <main class="bg-gray-50">
    <div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0">
      <a href="https://demo.themesberg.com/windster/" class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
        <img src="https://demo.themesberg.com/windster/images/logo.svg" class="h-10 mr-4" alt="Windster Logo">
        <span class="self-center text-2xl font-bold whitespace-nowrap">Dicas de Django</span>
      </a>
      <!-- Card -->
      <div class="bg-white shadow rounded-lg md:mt-0 w-full sm:max-w-screen-sm xl:p-0">
        <div class="p-6 sm:p-8 lg:p-16 space-y-8">
          <h2 class="text-2xl lg:text-3xl font-bold text-gray-900">
            Redefinição de senha enviada
          </h2>
          <p class="text-sm font-medium text-gray-500">
            Nós te enviamos um e-mail com instruções para configurar sua senha, se uma conta existe com o e-mail fornecido. Você receberá a mensagem em breve.
          </p>
          <p class="text-sm font-medium text-gray-500">
            Se você não recebeu um e-mail, por favor certifique-se que você forneceu o endereço que você está cadastrado, e verifique sua pasta de spam.
          </p>
        </div>
      </div>
    </div>
  </main>
{\% endblock content %}
```


# Dica 19.1 - Modelagem - OneToMany - Um pra Muitos - ForeignKey - Chave Estrangeira

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/wGTgSa1EFMw)

## Leia

* <https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html>

## ORM - Object Relational Mapping

### Rodando comandos direto no PostgreSQL.

```
docker-compose up -d

docker container exec -it dicas_de_django_db psql

\l  # lista os bancos
\c dicas_de_django_db  # conecta em dicas_de_django_db
\dt  # lista as tabelas

SELECT first_name, last_name, email FROM accounts_user;
SELECT * FROM accounts_user;

\q  # sair
```

### Rodando comandos pelo Django

#### Dentro do container

```
docker container exec -it dicas_de_django_app python manage.py shell_plus

User.objects.all()
```

Caso dê erro no container faça

```
docker-compose up --build  -d

docker container exec -it dicas_de_django_app python manage.py shell_plus

User.objects.all()
```

#### Fora do container

```
python manage.py shell_plus

User.objects.all()
```

Fazendo mais algumas queries.

```python
>>> users = User.objects.values('first_name', 'last_name', 'email')
>>> users
<QuerySet [{'first_name': '', 'last_name': '', 'email': 'admin@email.com'}, {'first_name': 'Regis', 'last_name': 'Santos', 'email': 'regis@email.com'}]>
>>> print(users.query)
SELECT "accounts_user"."first_name", "accounts_user"."last_name", "accounts_user"."email" FROM "accounts_user"

>>> users = User.objects.all()
>>> users
<QuerySet [<User: admin@email.com>, <User: regis@email.com>]>
>>> for user in users:
...     print(user)
... 
admin@email.com
regis@email.com

>>> for user in users:
...     print(user.first_name, user.email)
... 
 admin@email.com
Regis regis@email.com
```

E agora vamos aplicar um filtro.

```python
>>> user = User.objects.filter(email__icontains='regis').values('first_name', 'email')
>>> user
<QuerySet [{'first_name': 'Regis', 'email': 'regis@email.com'}]>
>>> print(user.query)
SELECT "accounts_user"."first_name", "accounts_user"."email" FROM "accounts_user" WHERE UPPER("accounts_user"."email"::text) LIKE UPPER(%regis%)
```

Leia [QuerySet API reference #icontains](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#icontains).

## Criando uma nova app

Vamos criar uma nova app chamada `bookstore`.

```
cd backend
python ../manage.py startapp bookstore
cd ..
```

Adicione em `INSTALLED_APPS`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'backend.bookstore',
]
```

Edite `urls.py`

```python
# urls.py
...
path('', include('backend.bookstore.urls', namespace='bookstore')),
```

Edite `bookstore/apps.py`

```python
# bookstore/apps.py
...
name = 'backend.bookstore'
```

Crie `bookstore/urls.py`

```python
# bookstore/urls.py
from django.urls import path

app_name = 'bookstore'

urlpatterns = [

]
```

## OneToMany - Um pra Muitos - ForeignKey - Chave Estrangeira

É o relacionamento onde usamos **chave estrangeira**, conhecido como **ForeignKey**.

![01\_fk.png](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-28fd9e3061c7dd0f1fa43ae5af70a3e92592ca00%2F01_fk.png?alt=media)

Ilustração feita com [excalidraw.com](https://excalidraw.com/).

Um **cliente** pode fazer vários **pedidos**, então para reproduzir o esquema acima, usamos o seguinte código:

```python
# bookstore/models.py
from django.db import models


class Customer(models.Model):
    first_name = models.CharField('nome', max_length=100)
    last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True)  # noqa E501
    email = models.EmailField('e-mail', max_length=50, unique=True)
    active = models.BooleanField('ativo', default=True)

    class Meta:
        ordering = ('first_name',)
        verbose_name = 'cliente'
        verbose_name_plural = 'clientes'

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


STATUS = (
    ('p', 'Pendente'),
    ('a', 'Aprovado'),
    ('c', 'Cancelado'),
)


class Ordered(models.Model):
    status = models.CharField(max_length=1, choices=STATUS, default='p')
    customer = models.ForeignKey(
        Customer,
        on_delete=models.SET_NULL,
        verbose_name='cliente',
        related_name='ordereds',
        null=True,
        blank=True
    )
    created = models.DateTimeField(
        'criado em',
        auto_now_add=True,
        auto_now=False
    )

    class Meta:
        ordering = ('-pk',)
        verbose_name = 'ordem de compra'
        verbose_name_plural = 'ordens de compra'

    def __str__(self):
        if self.customer:
            return f'{str(self.pk).zfill(3)}-{self.customer}'

        return f'{str(self.pk).zfill(3)}'
```

```python
# bookstore/admin.py
from django.contrib import admin

from .models import Customer, Ordered


@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email', 'active')
    search_fields = ('first_name', 'last_name')
    list_filter = ('active',)


@admin.register(Ordered)
class OrderedAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'customer', 'status')
    search_fields = (
        'customer__first_name',
        'customer__last_name',
        'customer__email',
    )
    list_filter = ('status',)
    date_hierarchy = 'created'
```

```
python manage.py makemigrations
python manage.py migrate
```

### Diagrama ER

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1ca3a529e7b91d48968d6f390d977c6f0831f838%2F01_fk_er.png?alt=media)

### Inserindo dados com django-seed

```
pip install django-seed

pip freeze | grep django-seed >> requirements.txt
```

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'django_seed',
    ...
]
```

Gerando os dados

```
python manage.py seed bookstore --number=3
```

### ORM

```python
python manage.py shell_plus


from backend.bookstore.models import Customer, Ordered

customers = Customer.objects.all()

for customer in customers:
    print(customer)

ordereds = Ordered.objects.all()

for ordered in ordereds:
    print(ordered)
```

### PostgreSQL e pgAdmin no Docker

Vamos usar o PostgreSQL rodando no Docker.

```
docker-compose up -d
```

Podemos ver tudo pelo pgAdmin, ou

```
docker container exec -it dicas_de_django_db psql
```

#### As tabelas

```
\c dicas_de_django_db  # conecta no banco dicas_de_django_db
\dt    # mostra todas as tabelas
```

#### Os registros

```
SELECT * FROM bookstore_ordered;

id | status |        created         | customer_id 
----+--------+------------------------+-------------
  1 | p      | 1976-01-05 07:04:10+00 |           1
  2 | p      | 2021-02-09 06:08:27+00 |           3
  3 | p      | 1986-08-13 02:54:02+00 |           1
```

#### Schema

```
SELECT column_name, data_type FROM information_schema.columns WHERE TABLE_NAME = 'bookstore_ordered';
 column_name |        data_type         
-------------+--------------------------
 id          | bigint
 created     | timestamp with time zone
 customer_id | bigint
 status      | character varying
(4 rows)
```

## DBeaver e CloudBeaver

### CloudBeaver

<https://github.com/dbeaver/cloudbeaver/wiki/Run-Docker-Container>

<https://cloudbeaver.io/doc/cloudbeaver.pdf>

Edite o `docker-compose.yml`

```
  cloudbeaver:
    container_name: dicas_de_django_cloudbeaver
    image: dbeaver/cloudbeaver:latest
    volumes:
       - /var/cloudbeaver/workspace:/opt/cloudbeaver/workspace
    ports:
      - 5052:8978
    networks:
      - dicas-de-django-network

```

E rode

```
docker-compose up -d
```

Entre no Portainer e entre no CloudBeaver.

Login e senha

```
Login: cbadmin
Pass: admin
```

Conexão com `dicas_de_django_db`.

### pgAdmin 4

```
User: admin
Password: admin
```

```
Port: 5432
Database: localhost
Username: postgres
Password: postgres
```

### DBeaver

```
Host: 0.0.0.0
Port: 5431
Database: dicas_de_django_db
Username: postgres
Password: postgres
```

### Jupyter Notebook

Para instalar o Jupyter digite

```
pip install jupyter
```

E para rodar digite

```
python manage.py shell_plus --notebook
```

Quando você tentar rodar

```
Customer.objects.all()
```

Você vai ter este erro

```
SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
```

Então edite o `settings.py`

```python
import os

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

```

Mas o código completo deve ser

```python
from backend.bookstore.models import Customer, Ordered
Customer.objects.all()

adam = Customer.objects.create(first_name='Adam', email='adam@email.com')
james = Customer.objects.create(first_name='James', email='james@email.com')

Customer.objects.all()

Ordered.objects.create(customer=adam)
Ordered.objects.create(customer=adam)
Ordered.objects.create(customer=james)
Ordered.objects.create(customer=james)
Ordered.objects.create(customer=james)

ordereds = Ordered.objects.all()

for ordered in ordereds:
    print(ordered)
    print(ordered.status)
    print(ordered.get_status_display())
    print(ordered.customer)
    print(ordered.customer.email)
```

Ou seja, a partir da ordem de compra conseguimos ver o cliente.

E como fazemos para a partir do cliente, ver as ordens de compra dele?

```python
adam.ordereds.all()
james.ordereds.all()
```

Ou seja, pegamos os dados pelo `related_name`.


# Dica 19.2 - Modelagem - OneToOne - Um pra Um

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/uqgy9MjOClQ)

## OneToOne - Um pra Um

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-e6730907359bd13aba962b1a3e16676baad6c0c5%2F02_one2one.png?alt=media)

```python
# bookstore/models.py

METHOD_PAYMENT = (
    ('di', 'dinheiro'),
    ('de', 'débito'),
    ('cr', 'crédito'),
    ('pix', 'Pix'),
)


class Sale(models.Model):
    ordered = models.OneToOneField(
        Ordered,
        on_delete=models.CASCADE,
        verbose_name='ordem de compra'
    )
    paid = models.BooleanField('pago', default=False)
    date_paid = models.DateTimeField('data de pagamento', null=True, blank=True)
    method = models.CharField('forma de pagamento', max_length=3, choices=METHOD_PAYMENT)  # noqa E501
    deadline = models.PositiveSmallIntegerField('prazo de entrega', default=15)
    created = models.DateTimeField(
        'criado em',
        auto_now_add=True,
        auto_now=False
    )

    class Meta:
        ordering = ('-pk',)
        verbose_name = 'venda'
        verbose_name_plural = 'vendas'

    def __str__(self):
        if self.ordered:
            return f'{str(self.pk).zfill(3)}-{self.ordered}'

        return f'{str(self.pk).zfill(3)}'
```

```python
# bookstore/admin.py
from .models import Sale


@admin.register(Sale)
class SaleAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'paid', 'date_paid', 'method', 'deadline')
    list_filter = ('paid', 'method')
    date_hierarchy = 'created'
```

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1c541d937cd9c5e8751d4dc6663a783dfeca44af%2F03_fk_one2one.png?alt=media)

```
python manage.py makemigrations
python manage.py migrate
```

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-e5f491f17c245613ae0d8f4dd22e27f33174b695%2F04_one2one_user_profile.png?alt=media)

```python
# core/models.py
from backend.accounts.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.PROTECT,
        verbose_name='usuário'
    )
    birthday = models.DateField('data de nascimento', null=True, blank=True)
    linkedin = models.URLField(null=True, blank=True)
    rg = models.CharField(max_length=10, null=True, blank=True)
    cpf = models.CharField(max_length=11, null=True, blank=True)

    class Meta:
        ordering = ('user__first_name',)
        verbose_name = 'perfil'
        verbose_name_plural = 'perfis'

    @property
    def full_name(self):
        return f'{self.user.first_name} {self.user.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()
```

```python
# core/admin.py
from backend.core.models import Profile


@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'birthday', 'linkedin', 'rg', 'cpf')
    search_fields = (
        'customer__first_name',
        'customer__last_name',
        'customer__email',
        'linkedin',
        'rg',
        'cpf'
    )
```

```
python manage.py makemigrations
python manage.py migrate
```

### Diagrama ER

**Obs:** Caso dê o **erro**

```
RelatedObjectDoesNotExist at /admin/login/

User has no profile.
```

Entre no shell e digite

```python
python manage.py shell_plus

from backend.accounts.models import User
from backend.core.models import Profile

users = User.objects.all()

for user in users:
    try:
        user.profile
    except Profile.DoesNotExist:
        profile = Profile(user=user)
        profile.save()
```

Então no shell ou no notebook experimente

```python
paul = User.objects.create(email='paul@email.com', first_name='Paul')
paul.profile
paul.profile.cpf = '987654321'
paul.profile.save()
```


# Dica 19.3 - Modelagem - ManyToMany - Muitos pra Muitos

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/nbynxIa8RNs)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-41ab0df6ed5987db7420005d59a946bcad5df9fa%2F06_m2m_author_book.png?alt=media)

```python
# bookstore/models.py
class Author(models.Model):
    first_name = models.CharField('nome', max_length=100)
    last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True)  # noqa E501

    class Meta:
        ordering = ('first_name',)
        verbose_name = 'autor'
        verbose_name_plural = 'autores'

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


class Book(models.Model):
    isbn = models.CharField(max_length=13, unique=True)
    title = models.CharField('título', max_length=255)
    rating = models.DecimalField('pontuação', max_digits=5, decimal_places=2, default=5)
    authors = models.ManyToManyField(
        Author,
        verbose_name='autores',
        blank=True
    )
    price = models.DecimalField('preço', max_digits=5, decimal_places=2)
    stock_min = models.PositiveSmallIntegerField(default=0)
    stock = models.PositiveSmallIntegerField(default=0)
    created = models.DateTimeField(
        'criado em',
        auto_now_add=True,
        auto_now=False
    )
    modified = models.DateTimeField(
        'modificado em',
        auto_now_add=False,
        auto_now=True
    )

    class Meta:
        ordering = ('title',)
        verbose_name = 'livro'
        verbose_name_plural = 'livros'

    def __str__(self):
        return f'{self.title}'
```

```python
# bookstore/admin.py
from .models import Author, Book


@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('__str__',)
    search_fields = ('first_name', 'last_name')


@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = (
        'isbn',
        '__str__',
        'rating',
        'price',
        'stock_min',
        'stock',
    )
    list_display_links = ('__str__',)
    search_fields = ('isbn', 'title')
```

```
python manage.py makemigrations
python manage.py migrate
```

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-07d14338fd1c9ed265a4fd1fe0f036b8006628dd%2F06_m2m_author_book_er.png?alt=media)

### Jupyter Notebook

```python
# Criando os autores
daniel = Author.objects.create(first_name='Daniel', last_name='Greenfeld')
audrey = Author.objects.create(first_name='Audrey', last_name='Greenfeld')

# Criando o livro
book = Book.objects.create(
    title='Two Scoops of Django',
    isbn='9780981467344',
    price=44.95
)

# Associando os autores ao livro
book.authors.add(daniel)
book.authors.add(audrey)

# Retornando os autores do livro
book.authors.all()

# Buscando por todos os livros do autor informado
Book.objects.filter(authors__last_name='Greenfeld').distinct()

# Buscando pelo autor cujo livro se chama 'Two Scoops of Django'
Author.objects.filter(book__title='Two Scoops of Django')
```

### Exemplo

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-f1f9ca09ef0621a2640fea420405efb78158affe%2F07_m2m_author_book_store.png?alt=media)

```python
# bookstore/models.py
class Store(models.Model):
    name = models.CharField('nome', max_length=255)
    books = models.ManyToManyField(
        Book,
        verbose_name='livros',
        blank=True
    )

    class Meta:
        ordering = ('name',)
        verbose_name = 'loja'
        verbose_name_plural = 'lojas'

    def __str__(self):
        return f'{self.name}'
```

```python
# bookstore/admin.py
from .models import Store

@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
    list_display = ('__str__',)
    search_fields = ('name',)
```

```
python manage.py makemigrations
python manage.py migrate
```

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-287ecb4130eb1a19d395f0a24d1f8fc7d4391150%2F07_m2m_author_book_store_er.png?alt=media)

### Exemplo

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-d9bdda52bfc966fdd41c471ef22a55646bb01ecd%2F08_m2m_fk.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-9b77d5e745068b60d374afb76cab7f188c7d933c%2F08_m2m_fk_er.png?alt=media)

```python
# bookstore/models.py
class Book(models.Model):
    ...
    publisher = models.ForeignKey(
        'Publisher',
        on_delete=models.SET_NULL,
        verbose_name='editora',
        related_name='books',
        null=True,
        blank=True
    )

class Publisher(models.Model):
    name = models.CharField('nome', max_length=255)

    class Meta:
        ordering = ('name',)
        verbose_name = 'editora'
        verbose_name_plural = 'editoras'

    def __str__(self):
        return f'{self.name}'
```

```python
# bookstore/admin.py
from .models import Publisher


@admin.register(Publisher)
class PublisherAdmin(admin.ModelAdmin):
    list_display = ('__str__',)
    search_fields = ('name',)
```

> Adicione `publisher` no `list_display` de `BookAdmin`.

```
python manage.py makemigrations
python manage.py migrate
```

### Jupyter Notebook

```python
book = Book.objects.filter(title__icontains='two scoops').first()
publisher = Publisher.objects.create(name='Feldroy')

book.publisher = publisher
book.save()

# Conferindo
book.publisher
```

### Exemplo

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1e41fa87f5b8232f9c13d2721afe9ccece039bbb%2F09_m2m_user_group.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-76cf270e30520324407a213c461705622501ede6%2F09_m2m_user_group_er.png?alt=media)

### Jupyter Notebook

```python
# Cria grupos
grupos = ['gerente', 'vendedor', 'comprador', 'entregador']

[Group.objects.create(name=grupo) for grupo in grupos]

Group.objects.count()
Group.objects.all()

# Cria usuário
gerson = User.objects.create(email='gerson@email.com', first_name='Gerson')

# Associa usuário a um grupo
vendedor = Group.objects.get(name='vendedor')
gerson.groups.add(vendedor)

# Retorna os grupos do usuário
gerson.groups.all()

# Cria usuário
jeremias = User.objects.create(email='jeremias@email.com', first_name='Jeremias')

# Associa usuário a um grupo
jeremias.groups.add(vendedor)

# Retorna todos os usuários do grupo 'vendedor'
User.objects.filter(groups__name='vendedor')
```


# Dica 19.4 - Modelagem - Abstract Inheritance - Herança Abstrata

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/0o-Zy1TgI0E)

## Abstract Inheritance - Herança Abstrata

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-165af121071537c2cef3f5d546be3707e940ba05%2F10_abstract.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-54f60bfcfe6f145f53adcc0bfda3827e9ab2d8f2%2F10_abstract_er.png?alt=media)

```python
# crm/models.py
from django.db import models


class Person(models.Model):
    first_name = models.CharField('nome', max_length=100)
    last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True)  # noqa E501
    email = models.EmailField('e-mail', max_length=50, unique=True)

    class Meta:
        abstract = True
        ordering = ('first_name',)

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


class Customer(Person):
    linkedin = models.URLField(max_length=255, null=True, blank=True)
    tags = models.TextField(null=True, blank=True)

    class Meta:
        verbose_name = 'cliente'
        verbose_name_plural = 'clientes'


class Seller(Person):
    internal = models.BooleanField('interno', default=True)
    commission = models.DecimalField('comissão', max_digits=7, decimal_places=2, default=0)  # noqa E501

    class Meta:
        verbose_name = 'vendedor'
        verbose_name_plural = 'vendedores'

```

```python
# crm/admin.py
from django.contrib import admin

from .models import Customer, Seller


@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email', 'linkedin')
    search_fields = ('first_name', 'last_name', 'email')


@admin.register(Seller)
class SellerAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email', 'internal', 'commission')
    search_fields = ('first_name', 'last_name', 'email')
    list_filter = ('internal',)

```

```
python manage.py makemigrations
python manage.py migrate
```

### TimeStampedModel

Um `abstract` muito comum é o `TimeStampedModel`.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-02f466dbd9521f2f746073abed3479a86458003a%2F11_timestampedmodel.png?alt=media)

```python
# core/models.py
class TimeStampedModel(models.Model):
    created = models.DateTimeField(
        'criado em',
        auto_now_add=True,
        auto_now=False
    )
    modified = models.DateTimeField(
        'modificado em',
        auto_now_add=False,
        auto_now=True
    )

    class Meta:
        abstract = True
```

E podemos herdar, por exemplo, em `crm` nos models `Customer` e `Seller`.

```python
# crm/models.py
from backend.core.models import TimeStampedModel

class Customer(Person, TimeStampedModel):
    ...

class Seller(Person, TimeStampedModel):
    ...

```

```
python manage.py makemigrations
python manage.py migrate
```

### Django seed

```
python manage.py seed crm --number=5
```

```python
python manage.py shell_plus

Customer.objects.all()

sellers = Seller.objects.all()

for seller in sellers:
    print(seller.first_name, seller.created)
```

Mas o `django-seed` altera o campo `created` também, então façamos

```python
from time import sleep

names = ['Huguinho', 'Zezinho', 'Luizinho']

for name in names:
    Seller.objects.create(
        first_name=name,
        last_name='Donald',
        email=f'{name.lower()}@email.com'
    )
    sleep(1.2)  # Só um testezinho de delay

sellers = Seller.objects.filter(last_name='Donald')

for seller in sellers:
    print(seller.first_name, seller.created)

```


# Dica 19.5 - Modelagem - Multi-table Inheritance - Herança Multi-tabela

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/p_BQe6tWhxY)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-ad0cad40cf6b4c0bd9dc9b601c6d3482a3eea1a4%2F12_mti.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1309ccf280d22617867d48974db92a0dc25eb1a8%2F12_mti_er.png?alt=media)

```python
# crm/models.py
class Pessoa(models.Model):
    first_name = models.CharField('nome', max_length=100)
    last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True)  # noqa E501
    email = models.EmailField('e-mail', max_length=50, unique=True)

    class Meta:
        ordering = ('first_name',)

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


class PF(Pessoa):
    '''
    Pessoa Física.
    É um multi-table inheritance (herança multi-tabela)
    porque a classe pai não tem abstract.
    '''
    cpf = models.CharField(max_length=11, null=True, blank=True)
    rg = models.CharField(max_length=10, null=True, blank=True)

    class Meta:
        verbose_name = 'Pessoa Física'
        verbose_name_plural = 'Pessoas Físicas'


class PJ(Pessoa):
    '''
    Pessoa Jurídica.
    '''
    cnpj = models.CharField(max_length=14, null=True, blank=True)
    ie = models.CharField('inscrição estadual', max_length=14, null=True, blank=True)  # noqa E501

    class Meta:
        verbose_name = 'Pessoa Jurídica'
        verbose_name_plural = 'Pessoas Jurídicas'

```

```python
# crm/admin.py
from .models import Pessoa, PF, PJ


@admin.register(Pessoa)
class PessoaAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email')
    search_fields = ('first_name', 'last_name', 'email')


@admin.register(PF)
class PFAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email', 'cpf', 'rg')
    search_fields = ('first_name', 'last_name', 'email')


@admin.register(PJ)
class PJAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'email', 'cnpj', 'ie')
    search_fields = ('first_name', 'last_name', 'email')

```

```
python manage.py makemigrations
python manage.py migrate
```

### Jupyter Notebook

```python
Pessoa.objects.create(first_name='Agnes', email='agnes@email.com')

PF.objects.create(first_name='James', email='james@email.com', cpf='72387711017')
PJ.objects.create(first_name='Zen', email='zen@email.com', cnpj='98980077000170')

Pessoa.objects.all().count()
Pessoa.objects.all()

PF.objects.all().values()
PJ.objects.all().values()
```


# Dica 19.6 - Modelagem - Proxy Model

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/jjxuoC_uoiU)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1fe0892e253addb70d0030c8321bd9ee7c16cc92%2F13_proxy_model.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-1f7160a6b2fe9959a1abb8ef9ef8ca3c80b6412d%2F13_proxy_model_er.png?alt=media)

## Criando app expense

Vamos criar uma nova app chamada `expense`.

```
cd backend
python ../manage.py startapp expense
cd ..
```

Adicione em `INSTALLED_APPS`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'backend.expense',
]
```

Edite `urls.py`

```python
# urls.py
...
path('', include('backend.expense.urls', namespace='expense')),
```

Edite `expense/apps.py`

```python
# expense/apps.py
...
name = 'backend.expense'
```

Crie `expense/urls.py`

```python
# expense/urls.py
from django.urls import path

app_name = 'expense'


urlpatterns = [
]

```

```python
# expense/models.py
from django.db import models

from backend.core.models import TimeStampedModel

from .managers import ExpenseManager, ReceiptManager


class Financial(TimeStampedModel):
    description = models.CharField('descrição', max_length=300)
    due_date = models.DateField('data de vencimento', null=True, blank=True)
    value = models.DecimalField('valor', max_digits=7, decimal_places=2)
    paid = models.BooleanField('pago?', default=False)
    # paid_to = models.ForeignKey()

    class Meta:
        ordering = ('-pk',)

    def __str__(self):
        return f'{self.description}'


class Expense(Financial):

    objects = ExpenseManager()

    class Meta:
        proxy = True
        verbose_name = 'Despesa'
        verbose_name_plural = 'Despesas'

    def save(self, *args, **kwargs):
        ''' Despesa é NEGATIVO. '''
        self.value = -1 * abs(self.value)
        super(Expense, self).save(*args, **kwargs)


class Receipt(Financial):

    objects = ReceiptManager()

    class Meta:
        proxy = True
        verbose_name = 'Recebimento'
        verbose_name_plural = 'Recebimentos'

    def save(self, *args, **kwargs):
        ''' Recebimento é POSITIVO. '''
        self.value = abs(self.value)
        super(Receipt, self).save(*args, **kwargs)

```

```python
# expense/managers.py
from django.db import models


class ExpenseManager(models.Manager):

    def get_queryset(self):
        return super(ExpenseManager, self).get_queryset().filter(value__lt=0)


class ReceiptManager(models.Manager):

    def get_queryset(self):
        return super(ReceiptManager, self).get_queryset().filter(value__gte=0)

```

```python
# expense/admin.py
from django.contrib import admin

from .models import Expense, Receipt


@admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'value', 'due_date', 'paid')
    search_fields = ('description',)
    list_filter = ('paid',)
    date_hierarchy = 'created'


@admin.register(Receipt)
class ReceiptAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'value', 'due_date', 'paid')
    search_fields = ('description',)
    list_filter = ('paid',)
    date_hierarchy = 'created'

```

```
python manage.py makemigrations
python manage.py migrate
```

> Mostrar as tabelas no pgAdmin4.

### Jupyter Notebook

```python
from pprint import pprint

Expense.objects.create(description='Fatura', value=1000)
Receipt.objects.create(description='Pagamento', value=1600)

financials = Financial.objects.all().values()

for financial in financials:
    pprint(financial)

expenses = Expense.objects.all().values()

for expense in expenses:
    pprint(expense)

# Extrato
financials = Financial.objects.all()

for financial in financials.order_by('created'):
    print(financial.id, financial.created.isoformat()[:10], financial.value, financial.description[:30])

from django.db.models import Sum

saldo = Financial.objects.aggregate(Sum('value'))

saldo

from datetime import date, timedelta
from random import randint
from faker import Faker

fake = Faker()

def gen_date_between(start_date='-30d', end_date='30d'):
    today = date.today()
    _start_date = int(start_date[:-1])
    _end_date = int(end_date[:-1])
    date_start = today + timedelta(days=_start_date)
    date_end = today + timedelta(days=_end_date)
    return fake.date_between_dates(date_start=date_start, date_end=date_end)


def gen_title():
    return fake.sentence()

# Criando mais dados fake
for i in range(10):
    Financial.objects.create(
        description=gen_title(),
        value=randint(-2000,2000),
        due_date=gen_date_between()
    )

# Extrato
financials = Financial.objects.all()

for financial in financials.order_by('due_date'):
    print(financial.id, financial.due_date.isoformat()[:10], financial.value, financial.description[:30])

from django.db.models.functions import TruncMonth

saldos = Financial.objects \
    .annotate(month=TruncMonth('due_date')) \
    .values('month') \
    .annotate(saldo=Sum('value')) \
    .order_by('month')

for saldo in saldos:
    print(saldo['month'].month, saldo['saldo'])

saldo = Financial.objects.aggregate(Sum('value'))

saldo
```

> Confira no LibreOffice ou Excel.


# Dica 19.7 - Modelagem - Resumo

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/m8RqKi47wf4)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-3c310d34c374e985308024f6927aeff0cebdf21d%2F03_many_to_many.png?alt=media)

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-2fee413055bee0b23c11ffe34733cda1a44306e0%2F06_proxy.png?alt=media)

Vamos mover `Profile` para a app `Accounts`.

```python
# accounts/models.py
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.PROTECT,
        verbose_name='usuário'
    )
    birthday = models.DateField('data de nascimento', null=True, blank=True)
    linkedin = models.URLField(null=True, blank=True)
    rg = models.CharField(max_length=10, null=True, blank=True)
    cpf = models.CharField(max_length=11, null=True, blank=True)

    class Meta:
        ordering = ('user__first_name',)
        verbose_name = 'perfil'
        verbose_name_plural = 'perfis'

    @property
    def full_name(self):
        return f'{self.user.first_name} {self.user.last_name or ""}'.strip()

    def __str__(self):
        return self.full_name


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

```

Edite admin.

```python
# accounts/admin.py
from .models import Profile, User


@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'birthday', 'linkedin', 'rg', 'cpf')
    search_fields = (
        'customer__first_name',
        'customer__last_name',
        'customer__email',
        'linkedin',
        'rg',
        'cpf'
    )

```

## Documentos

Agora, em `Accounts`, vamos criar um model chamado `Document`.

```python
# accounts/models.py
from backend.core.models import TimeStampedModel

class Document(TimeStampedModel):
    document = models.FileField(upload_to='')
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        ordering = ('pk',)
        verbose_name = 'Documento'
        verbose_name_plural = 'Documentos'

    def __str__(self):
        return f'{self.pk}'

```

Edite admin.py

```python
# accounts/admin.py
from .models import Document, Profile, User

@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'user')
```

## Produtos

Vamos criar uma nova app chamada `product`.

```
cd backend
python ../manage.py startapp product
cd ..
```

Edite `core/models.py`

```python
# core/models.py
class Active(models.Model):
    active = models.BooleanField('ativo', default=True)

    class Meta:
        abstract = True
```

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'backend.product',
]
```

Edite `product/apps.py`

```python
# product/apps.py
from django.apps import AppConfig


class ProductConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'backend.product'

```

Edite `product/models.py`

```python
# product/models.py
from django.db import models

from backend.core.models import Active, TimeStampedModel


class Category(models.Model):
    title = models.CharField('título', max_length=255, unique=True)

    class Meta:
        ordering = ('title',)
        verbose_name = 'categoria'
        verbose_name_plural = 'categorias'

    def __str__(self):
        return f'{self.title}'


class Product(TimeStampedModel, Active):
    title = models.CharField('título', max_length=255, unique=True)
    description = models.TextField('descrição', null=True, blank=True)
    category = models.ForeignKey(
        Category,
        on_delete=models.SET_NULL,
        verbose_name='categoria',
        related_name='products',
        null=True,
        blank=True
    )

    class Meta:
        ordering = ('title',)
        verbose_name = 'produto'
        verbose_name_plural = 'produtos'

    def __str__(self):
        return f'{self.title}'

```

Edite `product/admin.py`

```python
# product/admin.py
from django.contrib import admin

from .models import Category, Product


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('__str__',)
    search_fields = ('title',)


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'category')
    search_fields = ('title',)
    list_filter = ('category',)
    date_hierarchy = 'created'

```

### Fotos

Edite `product/models.py`

```python
# product/models.py

class Photo(TimeStampedModel):
    photo = models.ImageField(upload_to='')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    class Meta:
        ordering = ('pk',)
        verbose_name = 'Foto'
        verbose_name_plural = 'Fotos'

    def __str__(self):
        return f'{self.pk}'

```

Edite `product/admin.py`

```python
# product/admin.py
from .models import Category, Photo, Product


class PhotoInline(admin.TabularInline):
    model = Photo
    extra = 0


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = (PhotoInline,)
    list_display = ('__str__', 'category')
    search_fields = ('title',)
    list_filter = ('category',)
    date_hierarchy = 'created'
```

É necessário instalar o `Pillow`.

```
pip install Pillow

pip freeze | grep Pillow >> requirements.txt
```

## Imóveis

Vamos criar uma app chamada `realty`.

```
cd backend
python ../manage.py startapp realty
cd ..
```

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = [
    ...
    'backend.realty',
]
```

Edite `realty/apps.py`

```python
# realty/apps.py
from django.apps import AppConfig


class RealtyConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'backend.realty'

```

Edite `realty/models.py`

```python
# realty/models.py
from django.db import models

from .managers import RentManager, SaleManager

TYPE_OF_NEGOTIATION = (
    ('a', 'aluguel'),
    ('v', 'venda'),
)


class Realty(models.Model):
    name = models.CharField('nome', max_length=255)
    type_of_negotiation = models.CharField(
        'tipo de negociação',
        max_length=1,
        choices=TYPE_OF_NEGOTIATION,
    )
    price = models.DecimalField('preço', max_digits=12, decimal_places=2)

    class Meta:
        ordering = ('name',)
        verbose_name = 'imóvel'
        verbose_name_plural = 'imóveis'

    def __str__(self):
        return f'{self.name}'


class PropertyRent(Realty):

    objects = RentManager()

    class Meta:
        proxy = True
        verbose_name = 'aluguel'
        verbose_name_plural = 'aluguéis'

    def save(self, *args, **kwargs):
        self.type_of_negotiation = 'a'
        super(PropertyRent, self).save(*args, **kwargs)


class PropertySale(Realty):

    objects = SaleManager()

    class Meta:
        proxy = True
        verbose_name = 'venda'
        verbose_name_plural = 'vendas'

    def save(self, *args, **kwargs):
        self.type_of_negotiation = 'v'
        super(PropertySale, self).save(*args, **kwargs)

```

Edite `realty/managers.py`

```python
# realty/managers.py
from django.db import models


class RentManager(models.Manager):

    def get_queryset(self):
        return super(RentManager, self).get_queryset().filter(type_of_negotiation='a')


class SaleManager(models.Manager):

    def get_queryset(self):
        return super(SaleManager, self).get_queryset().filter(type_of_negotiation='v')

```

Edite `realty/admin.py`

```python
# realty/admin.py
from django.contrib import admin

from .models import PropertyRent, PropertySale


@admin.register(PropertyRent)
class PropertyRentAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'type_of_negotiation', 'price')
    search_fields = ('name',)
    list_filter = ('type_of_negotiation',)


@admin.register(PropertySale)
class PropertySaleAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'type_of_negotiation', 'price')
    search_fields = ('name',)
    list_filter = ('type_of_negotiation',)

```


# Dica 20 - Templates

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/uQ4OMkzoBvY)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

O básico sobre templates é:

## Subpasta no templates com o mesmo nome da app

Definir uma subpasta no templates com o mesmo nome da app.

```
mkdir -p accounts/templates/accounts
```

## Templates

### Lista de itens

```
touch accounts/templates/accounts/user_list.html
```

### Detalhes de um item

```
touch accounts/templates/accounts/user_detail.html
```

### Template de Formulário

```
touch accounts/templates/accounts/user_form.html
```

## Conteúdo de cada template

### user\_list.html

```html
{\% extends "base.html" %}

{\% block content %}
  <table>
    <thead>
      <tr>
        <th>Título</th>
      </tr>
    </thead>
    <tbody>
      {\% for object in object_list %}
        <tr>
          <td>{{ object }}</td>
        </tr>
      {\% endfor %}
    </tbody>
  </table>
{\% endblock content %}
```

### user\_detail.html

```html
{\% extends "base.html" %}

{\% block content %}
  <div>
    <div>
      <h1>Detalhes: {{ object }}</h1>
    </div>
    <div>
      <a href="javascript: history.go(-1)">Voltar</a>
    </div>

    <div>
      <img src="https://via.placeholder.com/500" alt="">
    </div>

    <!-- Título -->
    <div>
      <div>
        <div>Título</div>
        <div>{{ object.title }}</div>
      </div>
    </div>

    <!-- Descrição -->
    <div>
      <div>
        <div>Descrição</div>
        <div>{{ object.description }}</div>
      </div>
    </div>

  </div>
{\% endblock content %}
```

### user\_form.html

```html
{\% extends "base.html" %}
{\% load widget_tweaks %}

{\% block content %}
  <div>
    <div>
      <div>
        <h2>
          {\% if object.pk %}
            Editar
          {\% else %}
            Adicionar
          {\% endif %}
          Usuário
        </h2>

        {\% if form.errors %}
          {\% for error in form.non_field_errors %}
            <p class="text-red-500">{{ error }}</p>
          {\% endfor %}
        {\% endif %}

        <form action="." method="POST" enctype="multipart/form-data">
          {\% csrf_token %}

          {\% for field in form.visible_fields %}
            <div>
              <label>{{ field.label }}</label>
              {\% render_field field class="" %}
            </div>
            <span>{{ field.help_text }}</span>

            {\% for error in field.errors %}
              <span class="text-red-500">{{ error }}</span> <br>
            {\% endfor %}
          {\% endfor %}

          <div class="flex flex-col sm:flex-row">
            <button type="submit">Salvar</button>
            <a href="">
              Cancelar
            </a>
          </div>
        </form>
      </div>
    </div>
  </div>
{\% endblock content %}
```

## urls.py

```python
# accounts/urls.py
from django.contrib.auth.views import LogoutView
from django.urls import include, path

from backend.accounts import views as v

# A ordem das urls é importante por causa do slug, quando existir.
user_patterns = [
    path('', v.user_list, name='user_list'),  # noqa E501
    path('create/', v.user_create, name='user_create'),  # noqa E501
    path('<int:pk>/', v.user_detail, name='user_detail'),  # noqa E501
    path('<int:pk>/update/', v.user_update, name='user_update'),  # noqa E501
]

urlpatterns = [
    path('login/', v.MyLoginView.as_view(), name='login'),  # noqa E501
    path('logout/', LogoutView.as_view(), name='logout'),  # noqa E501
    ...

    path('users/', include(user_patterns)),
]
```

## views.py

```python
def user_list(request):
    template_name = 'accounts/user_list.html'
    object_list = User.objects.exclude(email='admin@email.com')
    context = {'object_list': object_list}
    return render(request, template_name, context)


def user_detail(request, pk):
    template_name = 'accounts/user_detail.html'
    instance = get_object_or_404(User, pk=pk)

    context = {'object': instance}
    return render(request, template_name, context)


def user_create(request):
    template_name = 'accounts/user_form.html'
    form = CustomUserForm(request.POST or None)

    context = {'form': form}
    return render(request, template_name, context)


def user_update(request, pk):
    template_name = 'accounts/user_form.html'
    instance = get_object_or_404(User, pk=pk)
    form = CustomUserForm(request.POST or None, instance=instance)

    context = {
        'object': instance,
        'form': form,
    }
    return render(request, template_name, context)
```

## forms.py

```python
class CustomUserForm(forms.ModelForm):
    first_name = forms.CharField(
        label='Nome',
        max_length=150,
    )
    last_name = forms.CharField(
        label='Sobrenome',
        max_length=150,
    )
    email = forms.EmailField(
        label='E-mail',
    )

    class Meta:
        model = User
        fields = (
            'first_name',
            'last_name',
            'email',
        )
```


# Dica 21 - Tentativas de Login

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/WLnJskC17PQ)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Edite `registration/login.html`

```html
{\% if form.errors %}
  {\% for error in form.non_field_errors %}
    <p class="text-red-500">{{ error }}</p>
  {\% endfor %}
{\% endif %}
```

## accounts/models.py

```python
class AuditEntry(TimeStampedModel):
    action = models.CharField(max_length=64)
    ip = models.GenericIPAddressField(null=True)
    email = models.CharField(max_length=256, null=True)

    def __unicode__(self):
        return f'{self.action}-{self.email}-{self.ip}'

    def __str__(self):
        return f'{self.action}-{self.email}-{self.ip}'


@receiver(user_logged_in)
def user_logged_in_callback(sender, request, user, **kwargs):
    ip = request.META.get('REMOTE_ADDR')
    AuditEntry.objects.create(
        action='user_logged_in',
        ip=ip,
        email=user.email
    )


@receiver(user_logged_out)
def user_logged_out_callback(sender, request, user, **kwargs):
    ip = request.META.get('REMOTE_ADDR')
    AuditEntry.objects.create(
        action='user_logged_out',
        ip=ip,
        email=user.email
    )


@receiver(user_login_password_failed)
def user_login_password_failed(sender, **kwargs):
    user = kwargs['user']
    AuditEntry.objects.create(
        action='user_login_password_failed',
        email=user.email
    )
```

## admin.py

```python
@admin.register(AuditEntry)
class AuditEntryAdmin(admin.ModelAdmin):
    list_display = ('action', 'email', 'ip', 'created')
    list_filter = ('action',)
```

## signals.py

```python
from django.dispatch import Signal

user_login_password_failed = Signal()
```

## urls.py

```python
path('login/', v.MyLoginView.as_view(), name='login'),  # noqa E501
```

## views.py

```python
class MyLoginView(LoginView):
    template_name = 'registration/login.html'
    form_class = MyAuthenticationForm

    def form_invalid(self, form):
        email = form.data.get('username')

        if email:
            try:
                user = User.objects.get(email=email)

                for error in form.errors.as_data()['__all__']:
                    if error.code == 'max_attempt':
                        # Envia email para o usuário resetar a senha.
                        send_mail_to_user_reset_password(self.request, user)

            except User.DoesNotExist:
                pass
            else:
                # Dispara o signal quando o usuário existe, mas a senha está errada.
                user_login_password_failed.send(
                    sender=__name__,
                    request=self.request,
                    user=user
                )

        return self.render_to_response(self.get_context_data(form=form))

    def form_valid(self, form):
        user = form.get_user()

        # Autentica usuário
        auth_login(self.request, user)

        # Zera o AuditEntry
        AuditEntry.objects.filter(
            email=user.email,
            action='user_login_password_failed'
        ).delete()

        return HttpResponseRedirect(self.get_success_url())
```

## forms.py

```python
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from backend.accounts.models import User

from .models import AuditEntry


class MyAuthenticationForm(AuthenticationForm):

    error_messages = {
        'invalid_login': _(
            "Please enter a correct %(username)s and password. Note that both "
            "fields may be case-sensitive."
        ),
        'inactive': _("This account is inactive."),
        'invalid_password': _("Senha inválida."),
        'max_attempt': _(
            "Você atingiu o número máximo de tentativas. "
            "Estamos te enviando um e-mail."
        ),
    }

    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username is not None and password:
            self.user_cache = authenticate(
                self.request,
                username=username,
                password=password
            )
            if self.user_cache is None:
                self.check_authentication_error(username)
            else:
                self.confirm_login_allowed(self.user_cache)
            return self.cleaned_data
        else:
            self.validation_field()

    def validation_field(self):
        raise ValidationError(
            'Os campos e-mail e senha devem ser preenchidos.',
            code='invalid_fields',
            params={'username': self.username_field.verbose_name},
        )

    def check_authentication_error(self, username):
        '''
        Verifica se foi erro de autenticação.
        '''
        try:
            user = User.objects.get(email=username)
        except User.DoesNotExist:
            raise self.get_invalid_login_error()
        else:
            self.check_max_attempts(user)
            raise self.get_invalid_password_error()

    def check_max_attempts(self, user):
        '''
        Verifica o número de tentativas de login.
        '''
        max_attempts = AuditEntry.objects.filter(
            email=user.email,
            action='user_login_password_failed'
        ).count()
        if max_attempts >= 2:
            # Envia email para o usuário resetar a senha.
            # Envia pela views.
            raise self.get_max_attempts_error()

    def get_invalid_password_error(self):
        '''
        Verifica se foi erro de senha inválida.
        '''
        return ValidationError(
            self.error_messages['invalid_password'],
            code='invalid_password',
            params={'username': self.username_field.verbose_name},
        )

    def get_invalid_login_error(self):
        '''
        Verifica se foi erro de login.
        '''
        return ValidationError(
            self.error_messages['invalid_login'],
            code='invalid_login',
            params={'username': self.username_field.verbose_name},
        )

    def get_max_attempts_error(self):
        '''
        Verifica se excedeu o número de tentativas de login.
        '''
        return ValidationError(
            self.error_messages['max_attempt'],
            code='max_attempt',
            params={'username': self.username_field.verbose_name},
        )
```

## views.py

```python
class MyLoginView(LoginView):
    template_name = 'registration/login.html'
    form_class = MyAuthenticationForm

    def form_invalid(self, form):
        email = form.data.get('username')

        if email:
            try:
                user = User.objects.get(email=email)

                for error in form.errors.as_data()['__all__']:
                    if error.code == 'max_attempt':
                        # Envia email para o usuário resetar a senha.
                        send_mail_to_user_reset_password(self.request, user)

            except User.DoesNotExist:
                pass
            else:
                # Dispara o signal quando o usuário existe, mas a senha está errada.
                user_login_password_failed.send(
                    sender=__name__,
                    request=self.request,
                    user=user
                )

        return self.render_to_response(self.get_context_data(form=form))
```

## services.py

```python
# accounts/services.py
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from .tokens import account_activation_token


def send_mail_to_user_reset_password(request, user):
    current_site = get_current_site(request)
    use_https = request.is_secure()
    subject = 'Aviso: Erro em tentativas de login.'
    message = render_to_string('email/account_reset_password.html', {
        'user': user,
        'protocol': 'https' if use_https else 'http',
        'domain': current_site.domain,
        'uid': urlsafe_base64_encode(force_bytes(user.pk)),
        'token': account_activation_token.make_token(user),
    })
    user.email_user(subject, message)
```


# Dica 22 - Validação

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/l47Y4bMJj24)

```python
# accounts/forms.py
# accounts/forms.py
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from backend.accounts.models import User

from .models import AuditEntry


class CustomUserForm(forms.ModelForm):
    first_name = forms.CharField(
        label='Nome',
        max_length=150,
    )
    last_name = forms.CharField(
        label='Sobrenome',
        max_length=150,
    )
    email = forms.EmailField(
        label='E-mail',
    )

    class Meta:
        model = User
        fields = (
            'first_name',
            'last_name',
            'email',
        )

    error_messages = {
        'invalid_first_character': _('O primeiro caractere deve ser uma letra.'),
    }

    def clean(self):
        cleaned_data = super().clean()
        first_name = self.data.get('first_name')
        last_name = cleaned_data['last_name']

        if first_name == last_name:
            raise ValidationError(_('Nome e Sobrenome não podem ser iguais.'))

        # return self.cleaned_data

    def clean_first_name(self):
        data = self.cleaned_data['first_name']

        if data[0].islower():
            raise ValidationError(_('A primeira letra deve ser maiúscula.'))

        if data[0].isdigit():
            raise self.get_invalid_first_character_error()

        return data

    def get_invalid_first_character_error(self):
        '''
        O primeiro caractere deve ser uma letra.
        '''
        return ValidationError(
            self.error_messages['invalid_first_character'],
            code='invalid_first_character'
        )
```


# Dica 23 - CRUD de produtos

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/TfC_1EcRBss)

```python
# product/views.py
from django.shortcuts import get_object_or_404, redirect, render

from .forms import ProductForm
from .models import Product


def product_list(request):
    template_name = 'product/product_list.html'
    object_list = Product.objects.all()
    context = {'object_list': object_list}
    return render(request, template_name, context)


def product_detail(request, pk):
    template_name = 'product/product_detail.html'
    instance = get_object_or_404(Product, pk=pk)

    context = {'object': instance}
    return render(request, template_name, context)


def product_create(request):
    template_name = 'product/product_form.html'
    form = ProductForm(request.POST or None)

    if request.method == 'POST' and form.is_valid():
        form.save()
        return redirect('product:product_list')

    context = {'form': form}
    return render(request, template_name, context)


def product_update(request, pk):
    template_name = 'product/product_form.html'
    instance = get_object_or_404(Product, pk=pk)
    form = ProductForm(request.POST or None, instance=instance)

    if request.method == 'POST' and form.is_valid():
        form.save()
        return redirect('product:product_list')

    context = {'form': form, 'object': instance}
    return render(request, template_name, context)


def product_delete(request, pk):
    instance = get_object_or_404(Product, pk=pk)
    instance.delete()
    return redirect('product:product_list')
```

```python
# product/forms.py
from django import forms

from .models import Product


class ProductForm(forms.ModelForm):
    required_css_class = 'required'

    class Meta:
        model = Product
        fields = ('title', 'description', 'category')
```


# Dica 24 - Alpine.js e Django

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/jTvfN0DctnQ)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

```
cd backend
python ../manage.py todo
cd ..
```

Edite `urls.py`

```python
path('todo/', include('backend.todo.urls', namespace='todo')),  # noqa E501
```

Edite `settings.py`

```python
INSTALLED_APPS = [
    ...
    'backend.todo',
]
```

Edite `todo/apps.py`

```python
name = 'backend.todo'
```

Edite `todo/models.py`

```python
from django.db import models
from backend.core.models import TimeStampedModel


class Todo(TimeStampedModel):
    task = models.CharField('tarefa', max_length=100)
    done = models.BooleanField('feita', default=False)

    class Meta:
        ordering = ('task',)
        verbose_name = 'tarefa'
        verbose_name_plural = 'tarefas'

    def __str__(self):
        return f'{self.task}'

    def to_dict(self):
        return {
            'id': self.id,
            'task': self.task,
            'done': self.done,
        }
```

Edite `todo/admin.py`

```python
from django.contrib import admin

from .models import Todo


@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'done')
    search_fields = ('task',)
    list_filter = ('done',)
```

Edite `todo/urls.py`

```python
from django.urls import include, path

from backend.todo import views as v

app_name = 'todo'

todo_patterns = [
    path('', v.todos, name='todos'),  # noqa E501
    path('<int:pk>/done/', v.todo_done, name='todo_done'),  # noqa E501
    path('<int:pk>/delete/', v.todo_delete, name='todo_delete'),  # noqa E501
]

urlpatterns = [
    path('', v.todo_list, name='todo_list'),  # noqa E501
    path('api/v1/', include(todo_patterns)),
]
```

Edite `todo/views.py`

```python
from django.views.decorators.http import require_http_methods
import json
from django.http import JsonResponse
from django.shortcuts import render

from .models import Todo
from django.views.decorators.csrf import csrf_exempt


def todo_list(request):
    '''
    Renderiza um template para as tarefas.
    '''
    template_name = 'todo/todo_list.html'
    return render(request, template_name)


def todos(request):
    '''
    Retorna as tarefas via API REST, ou adiciona uma nova.
    '''
    todos = Todo.objects.all()

    if request.method == 'POST':
        # Desserializa os dados.
        data = json.loads(request.body)

        # Salva a tarefa.
        todo = Todo.objects.create(task=data['task'])

        # Retorna o objeto.
        return JsonResponse(todo.to_dict())

    data = [todo.to_dict() for todo in todos]
    # Retorna uma lista.
    return JsonResponse(data, safe=False)


@csrf_exempt
@require_http_methods(['POST'])
def todo_done(request, pk):
    '''
    Conclui uma tarefa via API REST.
    '''
    data = json.loads(request.body)
    done = data.get('done')
    try:
        todo = Todo.objects.get(pk=pk)
        todo.done = done
        todo.save()
        return JsonResponse({'success': True})
    except Todo.DoesNotExist:
        return JsonResponse({'success': False})


@csrf_exempt
@require_http_methods(['DELETE'])
def todo_delete(request, pk):
    '''
    Deleta uma tarefa via API REST.
    '''
    todo = Todo.objects.get(pk=pk)
    todo.delete()
    return JsonResponse({'success': True})
```

Edite `core/templates/base.html`

```html
<!-- Alpine.js -->
<script src="//unpkg.com/alpinejs" defer></script>
```

Edite core/templates/includes/aside.html

```html
<li>
  <a href="{\% url 'todo:todo_list' %}" target="_blank" class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2">
    <svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"></path></svg>
    <span class="ml-3">Tarefas</span>
  </a>
</li>
```

Crie a pasta

```
mkdir -p backend/todo/templates/todo
touch backend/todo/templates/todo/todo_list.html
```

todo\_list.html versão base:

```html
<!-- todo_list.html versão base: -->
<!-- todo_list.html -->
{\% extends "base.html" %}
{\% load static %}

{\% block content %}
<!-- x-data="" -->
<div x-data="getTodos">
  <!-- START: header -->
  <div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5">
    <div class="mb-1 w-full">
      <div class="mb-4">
        <!-- { include "./includes/breadcrumb.html" %} -->
        <h1 class="text-xl sm:text-2xl font-semibold text-gray-900">Tarefas</h1>
      </div>
      <div class="sm:flex">
        <!-- @submit.prevent="" -->
        <form class="lg:pr-3">
          <div class="hidden sm:flex items-center sm:divide-x sm:divide-gray-100 mb-3 sm:mb-0">
            <label for="users-search" class="sr-only">Tarefa</label>
            <div class="mt-1 relative lg:w-64 xl:w-96">
              <!-- x-model="task" e x-ref="task" -->
              <input
                type="text"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="Nova Tarefa..."
              >
              <!-- x-ref="task" para receber o foco após o submit -->
            </div>
            <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-3 py-2 ml-1 w-full sm:w-auto text-center">Salvar</button>
          </div>
        </form>
      </div>
    </div>
  </div>
  <!-- END: header -->
  <!-- START: table -->
  <div class="flex flex-col">
    <div class="overflow-x-auto">
      <div class="align-middle inline-block min-w-full">
        <div class="shadow overflow-hidden">
          <table class="table-fixed min-w-full divide-y divide-gray-200">
            <thead class="bg-gray-100">
              <tr>
                <th scope="col" class="p-4">
                  <div class="flex items-center">
                    <input id="checkbox-all" aria-describedby="checkbox-1" type="checkbox"
                      class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded">
                    <label for="checkbox-all" class="sr-only">checkbox</label>
                  </div>
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                  Concluída
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                  Tarefa
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                </th>
              </tr>
            </thead>
            <tbody class="bg-white divide-y divide-gray-200">
              <!-- x-for -->
              <template>
                <tr class="hover:bg-gray-100">
                  <td class="p-4 w-4">
                    <div class="flex items-center">
                      <input id="checkbox-1" aria-describedby="checkbox-1" type="checkbox"
                        class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded">
                      <label for="checkbox-1" class="sr-only">checkbox</label>
                    </div>
                  </td>
                  <td class="p-4 w-4">
                    <!-- x-show="todo.done" -->
                    <button
                      class="text-green-500 w-10"
                      >
                      <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path>
                      </svg>
                    </button>
                  </td>
                  <!-- @click="toggleDone(todo)" -->
                  <td
                    class="p-4 flex items-center whitespace-nowrap space-x-6 mr-12 lg:mr-0"
                  >
                    <div class="text-sm font-normal text-gray-500">
                      <!-- :class="{ 'text-gray-500': todo.done, 'text-gray-900': !todo.done }"
                        x-text="todo.task" -->
                      <div
                        class="text-base font-semibold"
                      >
                        tarefa
                      </div>
                    </div>
                  </td>
                  <td class="p-4 whitespace-nowrap space-x-2">
                    <!-- @click="deleteTask(todo)" -->
                    <button type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm inline-flex items-center px-3 py-2 text-center">
                      <svg class="mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
                      Deletar
                    </button>
                  </td>
                </tr>
              </template>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
  <!-- END: table -->
  <!-- START: footer of table -->
  <div class="bg-white sticky sm:flex items-center w-full sm:justify-between bottom-0 right-0 border-t border-gray-200 p-4">
    <div class="flex items-center mb-4 sm:mb-0">
      <a href="#" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center">
        <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
      </a>
      <a href="#" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center mr-2">
        <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
      </a>
      <!-- x-text="'1-'+todos.length" -->
       <!-- x-text="todos.length" -->
      <span class="text-sm font-normal text-gray-500">Mostrando <span class="text-gray-900 font-semibold">1-20</span> de <span class="text-gray-900 font-semibold">2000</span></span>
    </div>
    <!-- { include "./includes/navigation_buttons.html" %} -->
  </div>
  <!-- END: footer of table -->
</div>
{\% endblock content %}

{\% block js %}
  <!-- https://adamj.eu/tech/2022/10/06/how-to-safely-pass-data-to-javascript-in-a-django-template/#separate-script-files -->
  <script
    src="{\% static 'js/todo.js' %}"
    data-csrf="{{ csrf_token }}"
  ></script>
{\% endblock js %}
```

```html
<!-- versão final -->
<!-- todo_list.html -->
{\% extends "base.html" %}
{\% load static %}

{\% block content %}
<!-- x-data="" -->
<div x-data="getTodos">
  <!-- START: header -->
  <div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5">
    <div class="mb-1 w-full">
      <div class="mb-4">
        <!-- { include "./includes/breadcrumb.html" %} -->
        <h1 class="text-xl sm:text-2xl font-semibold text-gray-900">Tarefas</h1>
      </div>
      <div class="sm:flex">
        <!-- @submit.prevent="" -->
        <form class="lg:pr-3" @submit.prevent="saveData">
          <div class="hidden sm:flex items-center sm:divide-x sm:divide-gray-100 mb-3 sm:mb-0">
            <label for="users-search" class="sr-only">Tarefa</label>
            <div class="mt-1 relative lg:w-64 xl:w-96">
              <!-- x-model="task" e x-ref="task" -->
              <input
                type="text"
                class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-cyan-600 focus:border-cyan-600 block w-full p-2.5"
                placeholder="Nova Tarefa..."
                x-model="task"
                x-ref="task"
              >
              <!-- x-ref="task" para receber o foco após o submit -->
            </div>
            <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-3 py-2 ml-1 w-full sm:w-auto text-center">Salvar</button>
          </div>
        </form>
      </div>
    </div>
  </div>
  <!-- END: header -->
  <!-- START: table -->
  <div class="flex flex-col">
    <div class="overflow-x-auto">
      <div class="align-middle inline-block min-w-full">
        <div class="shadow overflow-hidden">
          <table class="table-fixed min-w-full divide-y divide-gray-200">
            <thead class="bg-gray-100">
              <tr>
                <th scope="col" class="p-4">
                  <div class="flex items-center">
                    <input id="checkbox-all" aria-describedby="checkbox-1" type="checkbox"
                      class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded">
                    <label for="checkbox-all" class="sr-only">checkbox</label>
                  </div>
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                  Concluída
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                  Tarefa
                </th>
                <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
                </th>
              </tr>
            </thead>
            <tbody class="bg-white divide-y divide-gray-200">
              <!-- x-for -->
              <template x-for="todo in todos" :key="todo.id">
                <tr class="hover:bg-gray-100">
                  <td class="p-4 w-4">
                    <div class="flex items-center">
                      <input id="checkbox-1" aria-describedby="checkbox-1" type="checkbox"
                        class="bg-gray-50 border-gray-300 focus:ring-3 focus:ring-cyan-200 h-4 w-4 rounded">
                      <label for="checkbox-1" class="sr-only">checkbox</label>
                    </div>
                  </td>
                  <td class="p-4 w-4">
                    <!-- x-show="todo.done" -->
                    <button
                      class="text-green-500 w-10"
                      x-show="todo.done"
                      >
                      <svg fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path>
                      </svg>
                    </button>
                  </td>
                  <!-- @click="toggleDone(todo)" -->
                  <td
                    class="p-4 flex items-center whitespace-nowrap space-x-6 mr-12 lg:mr-0"
                    @click="toggleDone(todo)"
                  >
                    <div class="text-sm font-normal text-gray-500">
                      <!-- :class="{ 'text-gray-500': todo.done, 'text-gray-900': !todo.done }"
                        x-text="todo.task" -->
                      <div
                        class="text-base font-semibold"
                        :class="{ 'text-gray-500': todo.done, 'text-gray-900': !todo.done }"
                        x-text="todo.task"
                      >
                        tarefa
                      </div>
                    </div>
                  </td>
                  <td class="p-4 whitespace-nowrap space-x-2">
                    <!-- @click="deleteTask(todo)" -->
                    <button @click="deleteTask(todo)" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm inline-flex items-center px-3 py-2 text-center">
                      <svg class="mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
                      Deletar
                    </button>
                  </td>
                </tr>
              </template>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
  <!-- END: table -->
  <!-- START: footer of table -->
  <div class="bg-white sticky sm:flex items-center w-full sm:justify-between bottom-0 right-0 border-t border-gray-200 p-4">
    <div class="flex items-center mb-4 sm:mb-0">
      <a href="#" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center">
        <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
      </a>
      <a href="#" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center mr-2">
        <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
      </a>
      <!-- x-text="'1-'+todos.length" -->
       <!-- x-text="todos.length" -->
      <span class="text-sm font-normal text-gray-500">Mostrando <span class="text-gray-900 font-semibold" x-text="'1-'+todos.length">1-20</span> de <span class="text-gray-900 font-semibold" x-text="todos.length">2000</span></span>
    </div>
    <!-- { include "./includes/navigation_buttons.html" %} -->
  </div>
  <!-- END: footer of table -->
</div>
{\% endblock content %}

{\% block js %}
  <!-- https://adamj.eu/tech/2022/10/06/how-to-safely-pass-data-to-javascript-in-a-django-template/#separate-script-files -->
  <script
    src="{\% static 'js/todo.js' %}"
    data-csrf="{{ csrf_token }}"
  ></script>
{\% endblock js %}
```

Crie `todo.js`

```
touch backend/core/static/js/todo.js
```

```js
// todo.js
const data = document.currentScript.dataset
const csrftoken = data.csrf

const getTodos = () => ({
  url: '/todo/api/v1/',
  todos: [],
  task: '',
  required: false,

  init() {
    this.getData()
  },

  getData() {
    // Pega os dados no backend com fetch.
    fetch(this.url)
      .then(response => response.json())
      .then(data => {
        this.todos = data
      })
  },

  saveData() {
    // Verifica se task foi preenchido.
    if (!this.task) {
      this.required = true
      return
    }
    // Salva os dados no backend.
    fetch(this.url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken },
        body: JSON.stringify({
          task: this.task
        })
      })
      .then(response => response.json())
      .then((data) => {
        this.todos.push(data)
        this.task = ''
        this.$refs.task.focus()
      })
  },

  // Marca a tarefa como feita ou não.
  toggleDone(todo) {
    fetch(`/todo/api/v1/${todo.id}/done/`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          done: !todo.done
        })
      })
      .then(response => response.json())
      .then((data) => {
        if (data.success) todo.done = !todo.done
      })
  },

  deleteTask(todo) {
    fetch(`/todo/api/v1/${todo.id}/delete/`, {
        method: 'DELETE',
      })
      .then(response => response.json())
      .then((data) => {
        if (data.success) {
          this.todos.splice(this.todos.indexOf(todo), 1)
        }
      })
  }

})
```


# 091-25-dica-queryset

## Dica 25 - Criando Filtros poderosos no Django - Segredos do ORM

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/jrqd_zNMhFk)

<http://pythonclub.com.br/django-introducao-queries.html>

<https://docs.djangoproject.com/en/4.1/ref/models/querysets/>

### Preparando os dados

```
python manage.py seed product --number=500
python manage.py seed bookstore --number=500

python manage.py shell_plus
```

```python
from random import choice

categories = (
    'Acessórios de Moda',
    'Artigos de Ginástica e Esportes',
    'Artigos de Praia e Piscina',
    'Artigos para Presente',
    'Calçado Feminino',
    'Calçado Masculino',
    'Cama, Mesa e Banho',
    'Roupa Feminina',
    'Roupa Infantil',
    'Roupa Masculina',
)

# Deleta as categorias
Category.objects.all().delete()

# Cria as categorias com list comprehension
[Category.objects.create(title=title) for title in categories]

categories = Category.objects.all()
products = Product.objects.all()

for product in products:
    # Escolhe uma catetoria
    category = choice(categories)
    product.category = category

# Aplica a categoria em todos os produtos
Product.objects.bulk_update(products, ['category'])
```

### Relacionamento OneToOne e ForeignKey

Considere o models de `Product` e `Category`.

```python
# product/models.py
class Category(models.Model):
    title = models.CharField('título', max_length=255, unique=True)

    class Meta:
        ordering = ('title',)
        verbose_name = 'categoria'
        verbose_name_plural = 'categorias'

    def __str__(self):
        return f'{self.title}'


class Product(models.Model):
    title = models.CharField('título', max_length=255, unique=True)
    description = models.TextField('descrição', null=True, blank=True)
    category = models.ForeignKey(
        Category,
        on_delete=models.SET_NULL,
        verbose_name='categoria',
        related_name='products',
        null=True,
        blank=True,
    )

    class Meta:
        ordering = ('title',)
        verbose_name = 'produto'
        verbose_name_plural = 'produtos'

    def __str__(self):
        return f'{self.title}'
```

Vamos iterar por todos os produtos. E imprimir o nome da categoria.

```python
from django.db import connection

products = Product.objects.all()

for product in products:
    print(product.category)

print(len(connection.queries))
# 500
```

Note que foram feitas 500 consultas no banco de dados. E isso torna a nossa query lenta.

### select\_related

Agora faça

```python
from django.db import connection

products = Product.objects.select_related('category').all()

for product in products:
    print(product.category)

print(len(connection.queries))
# 1
```

O objetivo do `select_related` é realizar uma única query que une todos os models relacionados. Ele faz isso através de um JOIN na instrução SQL, então realiza o cache do atributo para que possa acessá-lo sem realizar uma nova consulta. Só que ele não funciona para **ManyToMany**, e nem para **Relacionamento Reverso**.

### Relacionamento Reverso

Por padrão o Django adiciona um relacionamento reverso quando sua tabela é referenciada por uma chave estrangeira.

Se não passar o parâmetro `related_name`, irá seguir o padrão `<nome_tabela>_set`.

```python
class Product(models.Model):
    ...
    category = models.ForeignKey(
        Category,
        ...
        related_name='products',
    )
```

```python
categories = Category.objects.all()

for category in categories:
    print(category.products.all())

len(connection.queries)
# 12
```

Significa que a partir da categoria eu consigo acessar todos os produtos.

### prefetch\_related

```python
categories = Category.objects.prefetch_related('products').all()

for category in categories:
    print(category.products.all())

len(connection.queries)
# 2
```

#### Exemplo com ManyToMany

Considere o `bookstore/models.py`

```python
class Author(models.Model):
    first_name = models.CharField('nome', max_length=100)
    last_name = models.CharField('sobrenome', max_length=255, null=True, blank=True)  # noqa E501

class Book(models.Model):
    title = models.CharField('título', max_length=255)
    authors = models.ManyToManyField(
        Author,
        verbose_name='autores',
        blank=True
    )
    ...
```

A partir do Livro, vamos acessar todos os autores dele.

```python
from django.db import connection

books = Book.objects.prefetch_related('authors')

for book in books:
    print(book.authors.all())

len(connection.queries)
# 2
```

A partir do Autor, vamos acessar todos os livros dele.

```python
from django.db import connection

authors = Author.objects.prefetch_related('book_set')

for author in authors:
    print(author.book_set.all())

len(connection.queries)
# 2
```

### Filtro Direto

Liste os **produtos** cuja categoria seja *Roupa Masculina*. E retorne o título da categoria e o título do produto.

```python
from django.db import connection

category = Category.objects.get(title='Roupa Masculina')

# Bad
# products = Product.objects.filter(category=category)

# Good
products = Product.objects.select_related('category').filter(category=category)

for product in products:
    print(product.category.title, product.title)

print(len(connection.queries))

# Ou
products = Product.objects.select_related('category').filter(category__title='Roupa Masculina')
```

### Filtro Reverso

Retorne todas as categorias, e a partir de cada categoria retorne todos os produtos desta categoria.

```python
from django.db import connection

categories = Category.objects.prefetch_related('products').all()

for category in categories:
    print(category.title, category.products.all(), '\n')
    # print(category.category_set.all())  # Caso você não tivesse definido o related_name.

print(len(connection.queries))
```

## Filtrando a partir de uma lista de dados

Para filtrar a partir de uma lista de dados usamos o subcomando `__in`.

**Exemplo:**

Dada a lista de categorias

```python
categories = ['Roupa Feminina', 'Roupa Masculina']
```

Filtre todas as roupas dessas categorias.

Então, façamos

```python
products = Product.objects.filter(category__title__in=categories)
products.count()
products
```

## Ordenando os items

Para ordenar os items da query usamos `order_by()`.

**Exemplo:**

```python
Product.objects.all().order_by('title')  # ordem crescente
Product.objects.all().order_by('-title')  # ordem decrescente
Product.objects.all().order_by('created')  # mais antigo primeiro
Product.objects.all().order_by('-created')  # mais recente primeiro
```

## Retornando uma lista de registros

Para retornar uma lista de registros usamos o `flat = True`.

**Exemplo:**

```python
Product.objects.all().values_list('id', flat=True)
```

## Campo de Busca no Django

### O Operador `AND`

Para usar o `AND` basta separar os parâmetros do filtro com vírgula.

```python
products = Product.objects.filter(title__icontains='camera', category__title__icontains='feminina')
products.count()
products
```

### O Operador `OR`

#### Método `Q()`

Para usar o `OR` vamos precisar do método `Q()`.

```python
from django.db.models import Q

search = 'camera'

# depois com
# search = 'artigos'

products = Product.objects.filter(
    Q(title__icontains=search)
    | Q(description__icontains=search)
    | Q(category__title__icontains=search)
)
products.count()
products
```

### Diferença entre `get` e `filter`.

O `get` retorna **um objeto**.

```python
category = Category.objects.get(title='Roupa Masculina')
type(category)
category
category.title
```

O `filter` retorna **uma lista**.

```python
categories = Category.objects.filter(title__icontains='Roupa')
type(categories)
categories
```

#### Tratando o erro DoesNotExists

```python
try:
    category = Category.objects.get(title='Roupa')
except Category.DoesNotExist:
    print('Item não existe')
```

#### Evitando o erro DoesNotExists

```python
category = Category.objects.filter(title='Roupa')
category
```

Retorna um QuerySet vazio.

Você também pode experimentar

```python
category = Category.objects.filter(title='Roupa Masculina').first()  # retorna um objeto
category = Category.objects.filter(title='Roupa').first()  # retorna None
```

### get\_or\_create

<https://docs.djangoproject.com/en/4.1/ref/models/querysets/#get-or-create>

```python
obj, created = Category.objects.get_or_create(title='Eletrônicos')
```

Na primeira teremos `created=True`. Na segunda teremos `created=False`.

## Criando o campo de busca

Edite `product/views.py`

```python
from django.db.models import Q

def product_list(request):
    template_name = 'product/product_list.html'
    object_list = Product.objects.all()

    search = request.GET.get('search')

    if search:
        object_list = object_list.filter(
            Q(title__icontains=search)
            | Q(description__icontains=search)
            | Q(category__title__icontains=search)
        )

    context = {'object_list': object_list}
    return render(request, template_name, context)
```

Edite `product/templates/product/includes/search.html`

```html
<div class="flex space-x-1 pl-0 sm:pl-2 mt-3 sm:mt-0">
    <a href="." class="w-1/2 text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-cyan-200 font-medium inline-flex items-center justify-center rounded-lg text-sm px-3 py-2 text-center sm:w-auto">
      Limpar
    </a>
```


# Dica 26 - Paginação e Breadcrumb

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/wuTJQSwF9gw)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

## Paginação

<https://docs.djangoproject.com/en/4.1/topics/pagination/#using-paginator-in-a-view-function>

Edite `product/views.py`

```python
# from django.views.generic import ListView

# class ProductListView(ListView):
#     model = Product
#     paginate_by = 10
```

```python
from django.core.paginator import Paginator

def product_list(request):
    template_name = 'product/product_list.html'
    object_list = Product.objects.all()

    search = request.GET.get('search')

    ...

    # https://docs.djangoproject.com/en/4.1/topics/pagination/#using-paginator-in-a-view-function
    items_per_page = 10
    paginator = Paginator(object_list, items_per_page)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    context = {
        'page_obj': page_obj,
        'items_count': page_obj.object_list.count(),
    }
    return render(request, template_name, context)
```

## Botões da Paginação

Edite `product/includes/navigation_buttons.html`

```html
<!-- navigation_buttons.html -->
<div class="flex items-center space-x-3">
  {\% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search }}"
  {\% endif %}
  {\% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}&search={{ request.GET.search }}"
  {\% endif %}
</div>
```

O `&search={{ request.GET.search }}` mantém a **busca paginada**.

```html
<!-- navigation_buttons.html -->
<div class="flex items-center space-x-3">
  {\% if page_obj.has_previous %}
    <a
      href="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search }}"
      class="flex-1 text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium inline-flex items-center justify-center rounded-lg text-sm px-3 py-2 text-center"
    >
      <svg class="-ml-1 mr-1 h-5 w-5"" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
        </svg>
        Anterior
        </a>
  {\% endif %}
  {\% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}&search={{ request.GET.search }}" class="flex-1 text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium inline-flex items-center justify-center rounded-lg text-sm px-3 py-2 text-center">
      Próximo
      <svg class="-mr-1 ml-1 h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
          </svg>
          </a>
  {\% endif %}
  </div>
```

## Arruma o contador de itens

```html
<div class="flex items-center mb-4 sm:mb-0">
  {\% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search }}" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center">
      <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
    </a>
  {\% endif %}
  {\% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}&search={{ request.GET.search }}" class="text-gray-500 hover:text-gray-900 cursor-pointer p-1 hover:bg-gray-100 rounded inline-flex justify-center mr-2">
      <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
    </a>
  {\% endif %}
  <span class="text-sm font-normal text-gray-500"><span class="text-gray-900 font-semibold">Página {{ page_obj.number }} de {{ page_obj.paginator.num_pages }}</span> - Mostrando <span class="text-gray-900 font-semibold">{{ items_count }}</span> de <span class="text-gray-900 font-semibold">{{ page_obj.paginator.count }}</span> items</span>
</div>
```

### Move os arquivos para a pasta `core`

```
mv backend/product/templates/product/includes/navigation_buttons.html backend/core/templates/includes/
mv backend/product/templates/product/includes/search.html backend/core/templates/includes/
```

Edite `product_list.html`

```html
{\% include "includes/search.html" %}
{\% include "includes/navigation_buttons.html" %}
```

## breadcrumb

```
mv backend/product/templates/product/includes/breadcrumb.html backend/core/templates/includes/
```

Edite `product_list.html`, `product_detail.html` e `product_form.html`

```html
{\% include "includes/breadcrumb.html" %}
```

Edite `product/models.py`

```python
class Product(TimeStampedModel):
    ...

    def list_url(self):
        return reverse_lazy('product:product_list')

    @property
    def verbose_name(self):
        return self._meta.verbose_name

    @property
    def verbose_name_plural(self):
        return self._meta.verbose_name_plural
```

Edite `product/views.py`

```python
def product_create(request):
    template_name = 'product/product_form.html'
    form = ProductForm(request.POST or None)

    if request.method == 'POST' and form.is_valid():
        form.save()
        return redirect('product:product_list')

    verbose_name_plural = form.instance._meta.verbose_name_plural
    context = {
        'form': form,
        'verbose_name_plural': verbose_name_plural
    }
    return render(request, template_name, context)
```

Edite core/includes/breadcrumb.html

```html
<!-- breadcrumb.html -->
<nav class="flex mb-5" aria-label="Breadcrumb">
  <ol class="inline-flex items-center space-x-1 md:space-x-2">
    <li class="inline-flex items-center">
      <a href="{\% url 'core:dashboard' %}" class="text-gray-700 hover:text-gray-900 inline-flex items-center">
        <svg class="w-5 h-5 mr-2.5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg>
        Home
      </a>
    </li>
    <li>
      <div class="flex items-center">
        <svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
        {\% if object.pk %}
          <a href="{{ object.list_url }}" class="text-gray-700 hover:text-gray-900 ml-1 md:ml-2 text-sm font-medium">
            {{ object.verbose_name_plural }}
          </a>
        {\% elif 'create' in request.path %}
          <a href="javascript:window.history.go(-1)" class="text-gray-700 hover:text-gray-900 ml-1 md:ml-2 text-sm font-medium">
            {{ verbose_name_plural }}
          </a>
        {\% else %}
          <a href="{{ page_obj.0.list_url }}" class="text-gray-700 hover:text-gray-900 ml-1 md:ml-2 text-sm font-medium">
            {{ page_obj.0.verbose_name_plural }}
          </a>
        {\% endif %}
      </div>
    </li>
    <li>
      <div class="flex items-center">
        <svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
        <span class="text-gray-400 ml-1 md:ml-2 text-sm font-medium" aria-current="page">
          {\% if 'create' in request.path %}
            Adicionar
          {\% elif object.pk and 'update' in request.path %}
            Editar
          {\% elif object.pk %}
            Detalhe
          {\% else %}
            Lista
          {\% endif %}
        </span>
      </div>
    </li>
  </ol>
</nav>
```


# Dica 27 - Signals

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/CZ7vUBLpoZc)

Doc: <https://docs.djangoproject.com/en/4.1/topics/signals/>

<https://docs.djangoproject.com/en/4.1/ref/signals/>

## Problema

Suponha que você queira enviar um e-mail de boas-vindas para um novo usuário.

Onde você faria isso? Na views?

E se você usasse mais views para fazer esse cadastro?

Você criaria uma função! Hum... pode ser.

Mas e se você cadastra-se um usuário pelo Admin?

**Outra situação:**

Suponha que você não tenha acesso ao model `User`.

Neste projeto nós temos acesso, mas no Django padrão nós não teríamos acesso.

Como você faria para, tanto na views, como no Admin, e até no shell do Django, saber se um model do `User` foi criado ou modificado?

Para isso nós temos o signals.

## O que é um Signal?

No Django, os "signals" são uma forma de enviar sinais ou notificações quando ocorre uma determinada ação no banco de dados ou em algum modelo específico. Esses sinais podem ser usados para executar ações adicionais ou enviar notificações quando determinadas mudanças ocorrem no modelo.

Toda vez que um model é criado, ou alterado, é enviado um sinal para a aplicação. E este sinal pode fazer alguma coisa.

### Exemplos

* Ao criar um novo **usuário**, criar um `Profile` pra ele.
* Ao criar um novo **usuário**, enviar um e-mail pra ele.
* Ao criar um novo **produto**, definir um slug automaticamente.

## Model Profile

Considere o model `Profile`.

```python
class Profile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.PROTECT,
        verbose_name='usuário'
    )
    birthday = models.DateField('data de nascimento', null=True, blank=True)
    linkedin = models.URLField(null=True, blank=True)
    rg = models.CharField(max_length=10, null=True, blank=True)
    cpf = models.CharField(max_length=11, null=True, blank=True)
```

Agora vamos considerar o seguinte:

Toda vez que eu criar um **novo usuário**, eu quero automaticamente criar um `Profile` pra ele.

```python
from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()
```

### Detalhando um pouco mais

No terminal digite

```
python manage.py shell_plus
```

Depois

```python
from django.contrib.auth import get_user_model

User = get_user_model()

User  # aqui nós vemos que o nosso User está em backend.accounts.models.User
```

#### Exemplo 1

Se fizermos

```python
instance = User.objects.create()
```

Podemos ter os signals `pre_save` e `post_save`.

A diferença é que o `pre_save` tem o `instance`, e o `post_save` tem o `instance` e o `created`.

```python
# pre_save -> instance
instance = User.objects.create()
# post_save -> instance, created=True
```

Note também que no `pre_save` você ainda não tem o id do objeto.

#### Exemplo 2

Neste outro exemplo, usando o comando `save()`

```python
# pre_save -> instance
instance.save()
# post_save -> instance, created=False
```

A diferença aqui é que em `post_save` o `created=False`.

#### Exemplo 3

Ao deletar temos os signals `pre_delete` e `post_delete`.

#### Exemplo 4

Agora vamos ao código

```python
# accounts/models.py

from django.db.models.signals import post_save
from django.dispatch import receiver

def user_created_handler(*args, **kwargs):
    print('Usuário criado com sucesso.')

post_save.connect(user_created_handler, sender=User)
```

Ou podemos usar o decorator `receiver`.

```python
@receiver(post_save, sender=User)
def user_created_handler(*args, **kwargs):
    print('Usuário criado com sucesso.')
    print(args, kwargs)
```

Quando adicionarmos um novo usuário...

```python
from django.contrib.auth import get_user_model

User = get_user_model()

User.objects.create_user(email='user01@email.com')

() {'signal': <django.db.models.signals.ModelSignal object at 0x7fa6a116b490>, 'sender': <class 'backend.accounts.models.User'>, 'instance': <User: user01@email.com>, 'created': True, 'update_fields': None, 'raw': False, 'using': 'default'}
```

Veja o

```python
'instance': <User: user01@email.com>, 'created': True
```

#### Exemplo 5

Podemos acrescentar mais parâmetros como

```python
@receiver(post_save, sender=User)
def user_created_handler(sender, instance, created, *args, **kwargs):
    print('Usuário criado com sucesso.')
    # print(args, kwargs)
    if created:
        print('Envia e-mail para', instance.email)
    else:
        print(instance.email, 'foi salvo.')
```

Salve alguns usuários no Admin e veja o resultado no terminal.

#### Exemplo 6

Agora veremos o `pre_save`.

```python
@receiver(pre_save, sender=User)
def user_pre_save_handler(sender, instance, *args, **kwargs):
    print(instance.email, instance.id)  # None
    # NÃO FAÇA ISSO -> instance.save()  # Loop infinito
```

Teste pelo Admin.

#### Exemplo 7 - Profile

Agora já conseguimos entender o signal do `Profile`.

```python
# accounts/models.py
from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()
```

#### Exemplo 8 - Envio de e-mail

```python
# accounts/models.py
@receiver(post_save, sender=User)
def send_email_on_user_creation(sender, instance, created, **kwargs):
    if created:
        send_mail(
            'New user created',
            f'A new user with email {instance.email} has been created.',
            'from@example.com',
            ['to@example.com'],
            fail_silently=False,
        )
```

## Model Product

Em `Product` vamos adicionar um `slug`.

```python
# product/models.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.text import slugify


class Product(TimeStampedModel):
    ...
    slug = models.SlugField(blank=True, null=True)


@receiver(pre_save, sender=Product)
def product_pre_save(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = slugify(instance.title)
```

## Bonus: colocando o Signals num arquivo separado

```
touch backend/product/signals.py
```

```python
# product/signals.py
from django.utils.text import slugify


def product_pre_save(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = slugify(instance.title)
```

Agora precisamos editar o arquivo `apps.py`.

```python
# product/apps.py
class ProductConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myproject.product'

    def ready(self):
        from django.db.models.signals import pre_save

        from .models import Product
        from .signals import product_pre_save

        pre_save.connect(product_pre_save, sender=Product)
```

Para finalizar vamos colocar o `slug` como `read_only` no Admin.

```python
# product/admin.py

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    ...
    list_display = ('__str__', 'slug', 'category')
    readonly_fields = ('slug',)
    ...
```


# Dica 28 - Gerando dados aleatórios com Faker - faker-commerce

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/otT4fVdsECc)

Crie um arquivo `gen_products.py` em `backend/product`.

```
touch backend/product/gen_products.py
```

E instale

```
pip install faker-commerce

pip freeze | grep faker-commerce >> requirements.txt
```

```python
# gen_products.py
'''
Gera 100 produtos (título e preço) randomicamente com faker-commerce.

pip install faker-commerce
'''

import csv
import random
import uuid

import faker_commerce
from faker import Faker

fake = Faker()
fake.add_provider(faker_commerce.Provider)


QUANTITY = 100


with open('/tmp/products.csv', 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['title', 'price'])
    for _ in range(QUANTITY):
        short_uuid = uuid.uuid4().hex[:8]
        title = f'{fake.ecommerce_name()} {short_uuid}'
        price = round(random.uniform(1, 100), 2)
        writer.writerow([title, price])

```

E finalmente rodamos

```
python backend/product/gen_products.py
```


# Dica 29 - Importando CSV

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/jHCK_izrQa0)

## Criando um novo comando no Django

```
python manage.py create_command core -n import_data
```

```python
# backend/core/management/commands/import_data.py
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = "My shiny new management command."

    def add_arguments(self, parser):
        parser.add_argument('sample', nargs='+')

    def handle(self, *args, **options):
        raise NotImplementedError()

```

## Importando CSV pelo shell do Django

Vamos acrescentar o campo `price` em `product`.

```python
# product/models.py

class Product(TimeStampedModel):
    ...
    price = models.DecimalField('preço', max_digits=9, decimal_places=2, null=True, blank=True)

```

```
python manage.py makemigrations
python manage.py migrate
```

Digite

```
python manage.py shell_plus
```

```python
import csv

from backend.product.models import Product


def csv_to_list(filename: str) -> list:
    '''
    Lê um csv e retorna um OrderedDict.
    '''
    with open(filename) as csv_file:
        reader = csv.DictReader(csv_file, delimiter=',')
        csv_data = [line for line in reader]
    return csv_data

data = csv_to_list('/tmp/products.csv')

data

[{'title': 'Pizza 03f6be37', 'price': '10.16'},
 {'title': 'Pizza fced66c2', 'price': '52.8'},
 {'title': 'Gently Used Shoes 7e7b5f9f', 'price': '72.97'},
 {'title': 'Fish 57f5b41a', 'price': '33.1'},
 {'title': 'Cheese 5355253b', 'price': '66.99'},
 ...
]

def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in data:
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)
    Product.objects.bulk_create(aux)


save_data(data)

```

## Importando CSV com o novo comando import\_data

```python
# backend/core/management/commands/import_data.py
from django.core.management.base import BaseCommand


def import_csv(filename):
    print(filename)


class Command(BaseCommand):
    help = 'Importa dados de um CSV.'

    def add_arguments(self, parser):
        parser.add_argument(
            '--filename_csv',
            '-fcsv',
            dest='filename_csv',
            help='Importa arquivo CSV.'
        )

    def handle(self, *args, **options):
        filename = options['filename_csv']
        import_csv(filename)

```

```
python manage.py import_data --help

python manage.py import_data --filename_csv '/tmp/products.csv'
```

Continuando

```python
import csv
import sys

from django.core.management.base import BaseCommand

from backend.product.models import Product


def csv_to_list(filename: str) -> list:
    '''
    Lê um csv e retorna um OrderedDict.
    '''
    with open(filename) as csv_file:
        reader = csv.DictReader(csv_file, delimiter=',')
        csv_data = [line for line in reader]
    return csv_data


def progressbar(it, prefix="", size=60, file=sys.stdout):
    count = len(it)

    def show(j):
        x = int(size * j / count)
        file.write("%s[%s%s] %i/%i\r" % (prefix, "#" * x, "." * (size - x), j, count))  # noqa E501
        file.flush()
    show(0)
    for i, item in enumerate(it):
        yield item
        show(i + 1)
    file.write("\n")
    file.flush()


def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in progressbar(data, 'Importando dados'):
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)
    Product.objects.bulk_create(aux)


def import_csv(filename):
    data = csv_to_list(filename)
    save_data(data)


class Command(BaseCommand):
    help = 'Importa dados de um CSV.'

    def add_arguments(self, parser):
        parser.add_argument(
            '--filename_csv',
            '-fcsv',
            dest='filename_csv',
            help='Importa arquivo CSV.'
        )

    def handle(self, *args, **options):
        filename_csv = options['filename_csv']

        Product.objects.all().delete()
        import_csv(filename_csv)

```

```
python manage.py import_data -fcsv '/tmp/products.csv'
```

Simulando o progressbar um pouco mais lento.

```python
from time import sleep


def save_data(data, slow_motion=None):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in progressbar(data, 'Importando dados'):
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        if slow_motion:  # <---
            obj.save()
            sleep(.02)
        else:
            aux.append(obj)

    if not slow_motion:  # <---
        Product.objects.bulk_create(aux)


def import_csv(filename, slow_motion=None):  # <---
    data = csv_to_list(filename)
    save_data(data, slow_motion)  # <---


class Command(BaseCommand):
    help = 'Importa dados de um CSV.'

    def add_arguments(self, parser):
        ...
        parser.add_argument(
            '--slow_motion',
            '-slow',
            action='store_true',
            help='Simula camera lenta.'
        )

    def handle(self, *args, **options):
        filename_csv = options['filename_csv']
        slow_motion = options['slow_motion']  # <---

        Product.objects.all().delete()
        import_csv(filename_csv, slow_motion)  # <---

```

```
python manage.py import_data -fcsv '/tmp/products.csv' -slow
```


# 096-30-import-csv-inmemoryuploadedfile

## Dica 30 - Importando CSV InMemoryUploadedFile

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/9JnJHDGt5BI)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

Edite `product/product_list.html`

```html
<!-- product_list.html -->
<button onclick="openImportModal()" class="w-1/2 text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-cyan-200 font-medium inline-flex items-center justify-center rounded-lg text-sm px-3 py-2 text-center sm:w-auto">
  <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
  </svg>
  Importar
</button>

  <!-- head -->
  <th scope="col" class="p-4 text-left text-xs font-medium text-gray-500 uppercase">
    Preço
  </th>

  <!-- body -->
  <td class="p-4 whitespace-nowrap text-base font-medium text-gray-900">R$ {{ object.price|default:"---" }}</td>

  {\% include "./includes/import_modal.html" %}

{\% block js %}
  <script>
    // ...

    const targetElImport = document.getElementById('import-modal')
    const importModal = new Modal(targetElImport)

    openImportModal = () => {
      importModal.show()
    }
    closeImportModal = () => {
      importModal.hide()
    }
  </script>
{\% endblock js %}
```

Edite `product/product_detail.html`

```html
<!-- Preço -->
<div class="block w-full overflow-x-auto">
  <div class="mt-2">
    <div class="text-sm font-normal text-gray-500">Preço</div>
    <div class="text-base font-semibold text-gray-900">R$ {{ object.price }}</div>
  </div>
</div>
```

Edite `product/includes/import_modal.html`

```html
<!-- import_modal.html -->
<div class="hidden overflow-x-hidden overflow-y-auto fixed top-4 left-0 right-0 md:inset-0 z-50 justify-center items-center h-modal sm:h-full" id="import-modal">
  <div class="relative w-full max-w-md px-4 h-full md:h-auto">
    <!-- Modal content -->
    <div class="bg-white rounded-lg shadow relative">
      <!-- Modal header -->
      <div class="flex justify-end p-2">
        <button type="button" onclick="closeImportModal()" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" data-modal-toggle="import-modal">
          <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
        </button>
      </div>
      <!-- Modal body -->
      <div class="p-6 pt-0 text-center">
        <form action="{\% url 'product:import_csv' %}" method="POST" enctype="multipart/form-data">
          {\% csrf_token %}

          <h3 class="text-xl font-normal text-gray-500 mt-5 mb-6">Importar dados de um CSV ou XLSX</h3>

          <input type="file" name="csv_file">

          <button type="submit" class="text-white bg-cyan-600 hover:bg-cyan-800 focus:ring-4 focus:ring-cyan-300 font-medium rounded-lg text-base inline-flex items-center px-3 py-2.5 text-center mr-2">
            Sim, importar
          </button>
          <a href="#" onclick="closeImportModal()" class="text-gray-900 bg-white hover:bg-gray-100 focus:ring-4 focus:ring-cyan-200 border border-gray-200 font-medium inline-flex items-center rounded-lg text-base px-3 py-2.5 text-center" data-modal-toggle="import-modal">
            Não, cancelar
          </a>
        </form>
      </div>
    </div>
  </div>
</div>
```

Edite `product/urls.py`

```python
# product/urls.py
path('import-csv/', v.import_csv, name='import_csv'),  # noqa E501
```

Edite `product/views.py`

```python
# product/views.py
from django.views.decorators.http import require_http_methods
from .services import csv_to_list_in_memory, save_data


@require_http_methods(['POST'])
def import_csv(request):
    csv_file = request.FILES.get('csv_file')
    data = csv_to_list_in_memory(csv_file)
    save_data(data)
    return redirect('product:product_list')
```

Edite `product/services.py`

```python
# product/services.py
import csv
import io

from .models import Product


def csv_to_list_in_memory(filename: str) -> list:
    '''
    Lê um csv InMemoryUploadedFile e retorna um OrderedDict.
    '''
    file = filename.read().decode('utf-8')
    reader = csv.DictReader(io.StringIO(file))
    # Gerando uma list comprehension
    data = [line for line in reader]
    return data


def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in data:
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)

    Product.objects.bulk_create(aux)

```

## Importando CSV pelo Admin com django-import-export

<https://django-import-export.readthedocs.io/en/latest/getting\\_started.html#importing-data>

<https://django-import-export.readthedocs.io/en/latest/advanced\\_usage.html#admin-integration>

### Instalação

```
pip install django-import-export

pip freeze | grep django-import-export >> requirements.txt
```

### Configuração

Edite `settings.py`

```python
# settings.py
INSTALLED_APPS = (
    ...
    'import_export',
)
```

Rode o comando

```
python manage.py collectstatic
```

Edite `product/admin.py`

```python
# product/admin.py
from django.contrib import admin
from import_export import resources
from import_export.admin import ImportExportModelAdmin

from .models import Category, Photo, Product


class ProductResource(resources.ModelResource):

    class Meta:
        model = Product


@admin.register(Product)
class ProductAdmin(ImportExportModelAdmin):
    resource_classes = [ProductResource]
    ...
```


# Dica 31 - Importando CSV com Pandas

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/SaCCzTRSuYg)

<https://towardsdatascience.com/pandas-vs-dask-vs-datatable-a-performance-comparison-for-processing-csv-files-3b0e0e98215e>

```
pip install pandas
pip freeze | grep pandas >> requirements.txt

python manage.py shell_plus --notebook
```

```python
import pandas as pd

df = pd.read_csv('/tmp/products.csv')

df

df.max()

df.min()

Product.objects.all().delete()

def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in data:
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)

    Product.objects.bulk_create(aux)

data = []

for row in df.itertuples():
    _dict = dict(title=row.title, price=row.price)
    data.append(_dict)

data

save_data(data)

Product.objects.all().count()
```


# Dica 32 - Importando CSV com Dask

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/xQIIaUrxUzY)

<https://towardsdatascience.com/pandas-vs-dask-vs-datatable-a-performance-comparison-for-processing-csv-files-3b0e0e98215e>

```
python -m pip install "dask[dataframe]"
pip freeze | grep dask >> requirements.txt
```

No Notebook digite

```python
import dask.dataframe as dd

df = dd.read_csv('/tmp/products.csv')

df.head()

df.tail()

Product.objects.all().delete()

def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in data:
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)

    Product.objects.bulk_create(aux)

data = []

for index, row in df.iterrows():
    _dict = dict(title=row.title, price=row.price)
    data.append(_dict)

save_data(data)

Product.objects.all().count()
```


# Dica 33 - Importando XLSX com OpenPyXL

[![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-b3d2f4124239955eec7072e99d8d0f8ab8cac2b9%2Fyoutube.png?alt=media)](https://youtu.be/_5pjqup_F64)

**Importante:** remova a `\` no meio das tags.

![](https://980509322-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Mfu9nhT31FhtY3TMBTi%2Fuploads%2Fgit-blob-c80bb8144ef90a579a7a4d4fd8bfb6a22340a07b%2Ftags.png?alt=media)

```python
import openpyxl

wb = openpyxl.load_workbook("/home/regis/Documentos/products.xlsx", data_only=True)

ws = wb.active

max_row = ws.max_row
max_col = ws.max_column

for row in ws.iter_rows(max_row=max_row, max_col=max_col):
    print(row[0].value, row[1].value)

Product.objects.all().delete()

def save_data(data):
    '''
    Salva os dados no banco.
    '''
    aux = []
    for item in data:
        title = item.get('title')
        price = item.get('price')
        obj = Product(
            title=title,
            price=price,
        )
        aux.append(obj)

    Product.objects.bulk_create(aux)

data = []

for row in ws.iter_rows(min_row=2, max_row=max_row, max_col=max_col):
    _dict = dict(title=row[0].value, price=row[1].value)
    data.append(_dict)

save_data(data)

Product.objects.all().count()
```

## Importando XLSX InMemoryUploadedFile

```html
<!-- import_modal.html -->
<form action="{\% url 'product:import_view' %}" method="POST" enctype="multipart/form-data">

<input type="file" name="filename">
```

```python
# product/urls.py
path('import/', v.import_view, name='import_view'),  # noqa E501
```

```python
# product/views.py
@require_http_methods(['POST'])
def import_view(request):
    filename = request.FILES.get('filename')

    if filename.name.endswith('.csv'):
        data = csv_to_list_in_memory(filename)
    else:
        wb = openpyxl.load_workbook(filename, data_only=True)
        ws = wb.active

        max_row = ws.max_row
        max_col = ws.max_column

        data = []

        for row in ws.iter_rows(min_row=2, max_row=max_row, max_col=max_col):
            _dict = dict(title=row[0].value, price=row[1].value)
            data.append(_dict)

    save_data(data)
    return redirect('product:product_list')
```




---

[Next Page](/llms-full.txt/1)

