├── .gitignore ├── README.md ├── contrib └── env_gen.py ├── img ├── 1400605.jpg ├── admin_tabular_inline.png ├── auth_user_add.png ├── band_contact.png ├── dr_strange_failure.gif ├── login.png ├── mtv1.png ├── mtv3.png ├── password-change-form.png └── thor.gif ├── manage.py ├── myproject ├── __init__.py ├── accounts │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── accounts │ │ │ ├── login.html │ │ │ └── signup.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── asgi.py ├── core │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── management │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── create_data.py │ │ │ └── hello.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── css │ │ │ ├── form.css │ │ │ ├── icons │ │ │ │ └── simple-line-icons.min.css │ │ │ ├── login.css │ │ │ └── style.css │ │ ├── fonts │ │ │ ├── Simple-Line-Icons.eot │ │ │ ├── Simple-Line-Icons.svg │ │ │ ├── Simple-Line-Icons.ttf │ │ │ ├── Simple-Line-Icons.woff │ │ │ └── Simple-Line-Icons.woff2 │ │ ├── img │ │ │ └── django-logo-negative.png │ │ └── js │ │ │ └── django-ajax-setup.js │ ├── templates │ │ ├── base.html │ │ ├── base_login.html │ │ ├── includes │ │ │ ├── nav.html │ │ │ └── pagination.html │ │ └── index.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── crm │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20210606_1948.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── crm │ │ │ ├── contact_form.html │ │ │ ├── person_bootstrap_form.html │ │ │ ├── person_confirm_delete.html │ │ │ ├── person_crispy_form.html │ │ │ ├── person_detail.html │ │ │ ├── person_form.html │ │ │ ├── person_form0.html │ │ │ ├── person_form1.html │ │ │ ├── person_form2.html │ │ │ ├── person_list.html │ │ │ ├── person_modal.html │ │ │ ├── person_photo_form.html │ │ │ └── person_vuejs_list.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── settings.py ├── urls.py ├── utils │ ├── progress_bar.py │ └── utils.py └── wsgi.py ├── passo-a-passo.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .DS_Store 132 | 133 | media/ 134 | staticfiles/ 135 | .idea 136 | .ipynb_checkpoints/ 137 | .vscode 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-forms-tutorial 2 | 3 | Tutorial sobre formulários do Django para Live no YouTube. 4 | 5 | 6 | ## Este projeto foi feito com: 7 | 8 | * [Python 3.9.4](https://www.python.org/) 9 | * [Django 3.2.4](https://www.djangoproject.com/) 10 | 11 | 12 | ## Como rodar o projeto? 13 | 14 | * Clone esse repositório. 15 | * Crie um virtualenv com Python 3. 16 | * Ative o virtualenv. 17 | * Instale as dependências. 18 | * Rode as migrações. 19 | 20 | ``` 21 | git clone https://github.com/rg3915/django-forms-tutorial.git 22 | cd django-forms-tutorial 23 | python -m venv .venv 24 | source .venv/bin/activate 25 | pip install -r requirements.txt 26 | python contrib/env_gen.py 27 | python manage.py migrate 28 | python manage.py createsuperuser --username="admin" --email="" 29 | ``` 30 | 31 | Leia o [passo-a-passo.md](passo-a-passo.md) 32 | 33 | ![login.png](img/login.png) 34 | 35 | --- 36 | 37 | ![band_contact](img/band_contact.png) 38 | 39 | -------------------------------------------------------------------------------- /contrib/env_gen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python SECRET_KEY generator. 3 | """ 4 | import random 5 | 6 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%^&*()" 7 | size = 50 8 | secret_key = "".join(random.sample(chars, size)) 9 | 10 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%_" 11 | size = 20 12 | password = "".join(random.sample(chars, size)) 13 | 14 | CONFIG_STRING = """ 15 | DEBUG=True 16 | SECRET_KEY=%s 17 | ALLOWED_HOSTS=127.0.0.1,.localhost,0.0.0.0 18 | 19 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME 20 | #POSTGRES_DB= 21 | #POSTGRES_USER= 22 | #POSTGRES_PASSWORD=%s 23 | #DB_HOST=localhost 24 | 25 | #DEFAULT_FROM_EMAIL= 26 | #EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 27 | #EMAIL_HOST=localhost 28 | #EMAIL_PORT= 29 | #EMAIL_HOST_USER= 30 | #EMAIL_HOST_PASSWORD= 31 | #EMAIL_USE_TLS=True 32 | """.strip() % (secret_key, password) 33 | 34 | # Writing our configuration file to '.env' 35 | with open('.env', 'w') as configfile: 36 | configfile.write(CONFIG_STRING) 37 | 38 | print('Success!') 39 | print('Type: cat .env') 40 | -------------------------------------------------------------------------------- /img/1400605.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/1400605.jpg -------------------------------------------------------------------------------- /img/admin_tabular_inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/admin_tabular_inline.png -------------------------------------------------------------------------------- /img/auth_user_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/auth_user_add.png -------------------------------------------------------------------------------- /img/band_contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/band_contact.png -------------------------------------------------------------------------------- /img/dr_strange_failure.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/dr_strange_failure.gif -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/login.png -------------------------------------------------------------------------------- /img/mtv1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/mtv1.png -------------------------------------------------------------------------------- /img/mtv3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/mtv3.png -------------------------------------------------------------------------------- /img/password-change-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/password-change-form.png -------------------------------------------------------------------------------- /img/thor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/img/thor.gif -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /myproject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/__init__.py -------------------------------------------------------------------------------- /myproject/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/accounts/__init__.py -------------------------------------------------------------------------------- /myproject/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /myproject/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'myproject.accounts' 7 | -------------------------------------------------------------------------------- /myproject/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /myproject/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /myproject/accounts/templates/accounts/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base_login.html" %} 2 | {% load static %} 3 | 4 | {% block title %}Login{% endblock title %} 5 | 6 | {% block content %} 7 |
8 |
9 |
10 |
11 |

Login

12 | 13 | 14 | {% if form.errors %} 15 | {% for error in form.non_field_errors %} 16 | 17 | {% endfor %} 18 | {% endif %} 19 | 20 |
21 | {% csrf_token %} 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | 34 | 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | django-logo-negative.png 54 |

Cadastre-se

55 |

Guia de autenticação do Django.

56 | Cadastre-se 57 |
58 |
59 |
60 |
61 |
62 | {% endblock content %} 63 | -------------------------------------------------------------------------------- /myproject/accounts/templates/accounts/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base_login.html" %} 2 | {% load static %} 3 | {% load widget_tweaks %} 4 | 5 | {% block title %}Signup{% endblock title %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 |

Cadastre-se

13 |

Crie sua conta.

14 | 15 |
16 | {% csrf_token %} 17 | {% for field in form.visible_fields %} 18 |
19 | 26 |
27 |
28 | 29 | {% if field.name == 'email' %} 30 | @ 31 | {% elif field.name == 'password1' or field.name == 'password2' %} 32 | 33 | {% elif field.name == 'username' %} 34 | 35 | {% else %} 36 | 37 | {% endif %} 38 | 39 |
40 | {% render_field field class="form-control" placeholder=field.label %} 41 |
42 | {{ field.help_text }} 43 | {% for error in field.errors %} 44 |
{{ error }} 45 | {% endfor %} 46 |
47 | {% endfor %} 48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 |
56 | django-logo-negative.png 57 |

Login

58 |

Faça login.

59 | Login 60 |
61 |
62 |
63 |
64 |
65 | {% endblock content %} 66 | -------------------------------------------------------------------------------- /myproject/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /myproject/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.views import LoginView, LogoutView 2 | from django.urls import path 3 | 4 | urlpatterns = [ 5 | path( 6 | 'login/', 7 | LoginView.as_view(template_name='accounts/login.html'), 8 | name='login' 9 | ), 10 | path('logout/', LogoutView.as_view(), name='logout'), 11 | ] 12 | -------------------------------------------------------------------------------- /myproject/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /myproject/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for myproject project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /myproject/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/__init__.py -------------------------------------------------------------------------------- /myproject/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /myproject/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'myproject.core' 7 | -------------------------------------------------------------------------------- /myproject/core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/management/commands/__init__.py -------------------------------------------------------------------------------- /myproject/core/management/commands/create_data.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from myproject.crm.models import Person 4 | from myproject.utils import utils as u 5 | from myproject.utils.progress_bar import progressbar 6 | 7 | 8 | def get_person(): 9 | first_name = u.gen_first_name() 10 | last_name = u.gen_last_name() 11 | d = dict( 12 | first_name=first_name, 13 | last_name=last_name, 14 | email=u.gen_email(first_name, last_name), 15 | ) 16 | return d 17 | 18 | 19 | def create_persons(): 20 | aux = [] 21 | for _ in progressbar(range(50), 'Persons'): 22 | data = get_person() 23 | obj = Person(**data) 24 | aux.append(obj) 25 | Person.objects.bulk_create(aux) 26 | 27 | 28 | class Command(BaseCommand): 29 | help = 'Create data.' 30 | 31 | def handle(self, *args, **options): 32 | self.stdout.write('Create data.') 33 | create_persons() 34 | -------------------------------------------------------------------------------- /myproject/core/management/commands/hello.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | 4 | class Command(BaseCommand): 5 | help = 'Print hello world.' 6 | 7 | def add_arguments(self, parser): 8 | # Argumento nomeado 9 | parser.add_argument( 10 | '--awards', '-a', 11 | action='store_true', 12 | help='Help of awards options.' 13 | ) 14 | 15 | def handle(self, *args, **options): 16 | self.stdout.write('Hello world.') 17 | if options['awards']: 18 | self.stdout.write('Awards') 19 | -------------------------------------------------------------------------------- /myproject/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/migrations/__init__.py -------------------------------------------------------------------------------- /myproject/core/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.contrib.auth.models import User 4 | from django.db import models 5 | from localflavor.br.br_states import STATE_CHOICES 6 | 7 | 8 | class UuidModel(models.Model): 9 | uuid = models.UUIDField(unique=True, editable=False, default=uuid.uuid4) 10 | 11 | class Meta: 12 | abstract = True 13 | 14 | 15 | class TimeStampedModel(models.Model): 16 | created = models.DateTimeField( 17 | 'criado em', 18 | auto_now_add=True, 19 | auto_now=False 20 | ) 21 | modified = models.DateTimeField( 22 | 'modificado em', 23 | auto_now_add=False, 24 | auto_now=True 25 | ) 26 | 27 | class Meta: 28 | abstract = True 29 | 30 | 31 | class CreatedBy(models.Model): 32 | created_by = models.ForeignKey( 33 | User, 34 | verbose_name='criado por', 35 | on_delete=models.SET_NULL, 36 | null=True, 37 | blank=True, 38 | ) 39 | 40 | class Meta: 41 | abstract = True 42 | 43 | 44 | class Address(models.Model): 45 | address = models.CharField( 46 | 'endereço', 47 | max_length=100, 48 | null=True, 49 | blank=True 50 | ) 51 | address_number = models.IntegerField('número', null=True, blank=True) 52 | complement = models.CharField( 53 | 'complemento', 54 | max_length=100, 55 | null=True, 56 | blank=True 57 | ) 58 | district = models.CharField( 59 | 'bairro', 60 | max_length=100, 61 | null=True, 62 | blank=True 63 | ) 64 | city = models.CharField('cidade', max_length=100, null=True, blank=True) 65 | uf = models.CharField( 66 | 'UF', 67 | max_length=2, 68 | choices=STATE_CHOICES, 69 | null=True, 70 | blank=True 71 | ) 72 | cep = models.CharField('CEP', max_length=9, null=True, blank=True) 73 | country = models.CharField( 74 | 'país', 75 | max_length=50, 76 | default='Brasil', 77 | null=True, 78 | blank=True 79 | ) 80 | 81 | class Meta: 82 | abstract = True 83 | 84 | def to_dict_base(self): 85 | return { 86 | 'address': self.address, 87 | 'address_number': self.address_number, 88 | 'complement': self.complement, 89 | 'district': self.district, 90 | 'city': self.city, 91 | 'uf': self.uf, 92 | 'cep': self.cep, 93 | } 94 | 95 | 96 | class Document(models.Model): 97 | cpf = models.CharField( 98 | 'CPF', 99 | max_length=11, 100 | unique=True, 101 | null=True, 102 | blank=True 103 | ) 104 | rg = models.CharField('RG', max_length=11, null=True, blank=True) 105 | cnh = models.CharField('CNH', max_length=20, null=True, blank=True) 106 | 107 | class Meta: 108 | abstract = True 109 | 110 | def to_dict_base(self): 111 | return { 112 | 'cpf': self.cpf, 113 | 'rg': self.rg, 114 | 'cnh': self.cnh, 115 | } 116 | 117 | 118 | class Active(models.Model): 119 | active = models.BooleanField('ativo', default=True) 120 | exist_deleted = models.BooleanField( 121 | 'existe/deletado', 122 | default=True, 123 | help_text='Se for True o item existe. Se for False o item foi deletado.' 124 | ) 125 | 126 | class Meta: 127 | abstract = True 128 | -------------------------------------------------------------------------------- /myproject/core/static/css/form.css: -------------------------------------------------------------------------------- 1 | span.required:after { 2 | content: "*"; 3 | color: red; 4 | } -------------------------------------------------------------------------------- /myproject/core/static/css/icons/simple-line-icons.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:simple-line-icons;src:url(../../fonts/Simple-Line-Icons.eot);src:url(../../fonts/Simple-Line-Icons.eot) format('embedded-opentype'),url(../../fonts/Simple-Line-Icons.woff2) format('woff2'),url(../../fonts/Simple-Line-Icons.ttf) format('truetype'),url(../../fonts/Simple-Line-Icons.woff) format('woff'),url(../../fonts/Simple-Line-Icons.svg#simple-line-icons) format('svg');font-weight:400;font-style:normal}.icon-action-redo,.icon-action-undo,.icon-anchor,.icon-arrow-down,.icon-arrow-down-circle,.icon-arrow-left,.icon-arrow-left-circle,.icon-arrow-right,.icon-arrow-right-circle,.icon-arrow-up,.icon-arrow-up-circle,.icon-badge,.icon-bag,.icon-ban,.icon-basket,.icon-basket-loaded,.icon-bell,.icon-book-open,.icon-briefcase,.icon-bubble,.icon-bubbles,.icon-bulb,.icon-calculator,.icon-calendar,.icon-call-end,.icon-call-in,.icon-call-out,.icon-camera,.icon-camrecorder,.icon-chart,.icon-check,.icon-chemistry,.icon-clock,.icon-close,.icon-cloud-download,.icon-cloud-upload,.icon-compass,.icon-control-end,.icon-control-forward,.icon-control-pause,.icon-control-play,.icon-control-rewind,.icon-control-start,.icon-credit-card,.icon-crop,.icon-cup,.icon-cursor,.icon-cursor-move,.icon-diamond,.icon-direction,.icon-directions,.icon-disc,.icon-dislike,.icon-doc,.icon-docs,.icon-drawer,.icon-drop,.icon-earphones,.icon-earphones-alt,.icon-emotsmile,.icon-energy,.icon-envelope,.icon-envelope-letter,.icon-envelope-open,.icon-equalizer,.icon-event,.icon-exclamation,.icon-eye,.icon-eyeglass,.icon-feed,.icon-film,.icon-fire,.icon-flag,.icon-folder,.icon-folder-alt,.icon-frame,.icon-game-controller,.icon-ghost,.icon-globe,.icon-globe-alt,.icon-graduation,.icon-graph,.icon-grid,.icon-handbag,.icon-heart,.icon-home,.icon-hourglass,.icon-info,.icon-key,.icon-layers,.icon-like,.icon-link,.icon-list,.icon-location-pin,.icon-lock,.icon-lock-open,.icon-login,.icon-logout,.icon-loop,.icon-magic-wand,.icon-magnet,.icon-magnifier,.icon-magnifier-add,.icon-magnifier-remove,.icon-map,.icon-menu,.icon-microphone,.icon-minus,.icon-mouse,.icon-music-tone,.icon-music-tone-alt,.icon-mustache,.icon-note,.icon-notebook,.icon-options,.icon-options-vertical,.icon-organization,.icon-paper-clip,.icon-paper-plane,.icon-paypal,.icon-pencil,.icon-people,.icon-phone,.icon-picture,.icon-pie-chart,.icon-pin,.icon-plane,.icon-playlist,.icon-plus,.icon-power,.icon-present,.icon-printer,.icon-puzzle,.icon-question,.icon-refresh,.icon-reload,.icon-rocket,.icon-screen-desktop,.icon-screen-smartphone,.icon-screen-tablet,.icon-settings,.icon-share,.icon-share-alt,.icon-shield,.icon-shuffle,.icon-size-actual,.icon-size-fullscreen,.icon-social-behance,.icon-social-dribbble,.icon-social-dropbox,.icon-social-facebook,.icon-social-foursqare,.icon-social-github,.icon-social-google,.icon-social-instagram,.icon-social-linkedin,.icon-social-pinterest,.icon-social-reddit,.icon-social-skype,.icon-social-soundcloud,.icon-social-spotify,.icon-social-steam,.icon-social-stumbleupon,.icon-social-tumblr,.icon-social-twitter,.icon-social-vkontakte,.icon-social-youtube,.icon-speech,.icon-speedometer,.icon-star,.icon-support,.icon-symbol-female,.icon-symbol-male,.icon-tag,.icon-target,.icon-trash,.icon-trophy,.icon-umbrella,.icon-user,.icon-user-female,.icon-user-follow,.icon-user-following,.icon-user-unfollow,.icon-vector,.icon-volume-1,.icon-volume-2,.icon-volume-off,.icon-wallet,.icon-wrench{font-family:simple-line-icons;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-user:before{content:"\e005"}.icon-people:before{content:"\e001"}.icon-user-female:before{content:"\e000"}.icon-user-follow:before{content:"\e002"}.icon-user-following:before{content:"\e003"}.icon-user-unfollow:before{content:"\e004"}.icon-login:before{content:"\e066"}.icon-logout:before{content:"\e065"}.icon-emotsmile:before{content:"\e021"}.icon-phone:before{content:"\e600"}.icon-call-end:before{content:"\e048"}.icon-call-in:before{content:"\e047"}.icon-call-out:before{content:"\e046"}.icon-map:before{content:"\e033"}.icon-location-pin:before{content:"\e096"}.icon-direction:before{content:"\e042"}.icon-directions:before{content:"\e041"}.icon-compass:before{content:"\e045"}.icon-layers:before{content:"\e034"}.icon-menu:before{content:"\e601"}.icon-list:before{content:"\e067"}.icon-options-vertical:before{content:"\e602"}.icon-options:before{content:"\e603"}.icon-arrow-down:before{content:"\e604"}.icon-arrow-left:before{content:"\e605"}.icon-arrow-right:before{content:"\e606"}.icon-arrow-up:before{content:"\e607"}.icon-arrow-up-circle:before{content:"\e078"}.icon-arrow-left-circle:before{content:"\e07a"}.icon-arrow-right-circle:before{content:"\e079"}.icon-arrow-down-circle:before{content:"\e07b"}.icon-check:before{content:"\e080"}.icon-clock:before{content:"\e081"}.icon-plus:before{content:"\e095"}.icon-minus:before{content:"\e615"}.icon-close:before{content:"\e082"}.icon-event:before{content:"\e619"}.icon-exclamation:before{content:"\e617"}.icon-organization:before{content:"\e616"}.icon-trophy:before{content:"\e006"}.icon-screen-smartphone:before{content:"\e010"}.icon-screen-desktop:before{content:"\e011"}.icon-plane:before{content:"\e012"}.icon-notebook:before{content:"\e013"}.icon-mustache:before{content:"\e014"}.icon-mouse:before{content:"\e015"}.icon-magnet:before{content:"\e016"}.icon-energy:before{content:"\e020"}.icon-disc:before{content:"\e022"}.icon-cursor:before{content:"\e06e"}.icon-cursor-move:before{content:"\e023"}.icon-crop:before{content:"\e024"}.icon-chemistry:before{content:"\e026"}.icon-speedometer:before{content:"\e007"}.icon-shield:before{content:"\e00e"}.icon-screen-tablet:before{content:"\e00f"}.icon-magic-wand:before{content:"\e017"}.icon-hourglass:before{content:"\e018"}.icon-graduation:before{content:"\e019"}.icon-ghost:before{content:"\e01a"}.icon-game-controller:before{content:"\e01b"}.icon-fire:before{content:"\e01c"}.icon-eyeglass:before{content:"\e01d"}.icon-envelope-open:before{content:"\e01e"}.icon-envelope-letter:before{content:"\e01f"}.icon-bell:before{content:"\e027"}.icon-badge:before{content:"\e028"}.icon-anchor:before{content:"\e029"}.icon-wallet:before{content:"\e02a"}.icon-vector:before{content:"\e02b"}.icon-speech:before{content:"\e02c"}.icon-puzzle:before{content:"\e02d"}.icon-printer:before{content:"\e02e"}.icon-present:before{content:"\e02f"}.icon-playlist:before{content:"\e030"}.icon-pin:before{content:"\e031"}.icon-picture:before{content:"\e032"}.icon-handbag:before{content:"\e035"}.icon-globe-alt:before{content:"\e036"}.icon-globe:before{content:"\e037"}.icon-folder-alt:before{content:"\e039"}.icon-folder:before{content:"\e089"}.icon-film:before{content:"\e03a"}.icon-feed:before{content:"\e03b"}.icon-drop:before{content:"\e03e"}.icon-drawer:before{content:"\e03f"}.icon-docs:before{content:"\e040"}.icon-doc:before{content:"\e085"}.icon-diamond:before{content:"\e043"}.icon-cup:before{content:"\e044"}.icon-calculator:before{content:"\e049"}.icon-bubbles:before{content:"\e04a"}.icon-briefcase:before{content:"\e04b"}.icon-book-open:before{content:"\e04c"}.icon-basket-loaded:before{content:"\e04d"}.icon-basket:before{content:"\e04e"}.icon-bag:before{content:"\e04f"}.icon-action-undo:before{content:"\e050"}.icon-action-redo:before{content:"\e051"}.icon-wrench:before{content:"\e052"}.icon-umbrella:before{content:"\e053"}.icon-trash:before{content:"\e054"}.icon-tag:before{content:"\e055"}.icon-support:before{content:"\e056"}.icon-frame:before{content:"\e038"}.icon-size-fullscreen:before{content:"\e057"}.icon-size-actual:before{content:"\e058"}.icon-shuffle:before{content:"\e059"}.icon-share-alt:before{content:"\e05a"}.icon-share:before{content:"\e05b"}.icon-rocket:before{content:"\e05c"}.icon-question:before{content:"\e05d"}.icon-pie-chart:before{content:"\e05e"}.icon-pencil:before{content:"\e05f"}.icon-note:before{content:"\e060"}.icon-loop:before{content:"\e064"}.icon-home:before{content:"\e069"}.icon-grid:before{content:"\e06a"}.icon-graph:before{content:"\e06b"}.icon-microphone:before{content:"\e063"}.icon-music-tone-alt:before{content:"\e061"}.icon-music-tone:before{content:"\e062"}.icon-earphones-alt:before{content:"\e03c"}.icon-earphones:before{content:"\e03d"}.icon-equalizer:before{content:"\e06c"}.icon-like:before{content:"\e068"}.icon-dislike:before{content:"\e06d"}.icon-control-start:before{content:"\e06f"}.icon-control-rewind:before{content:"\e070"}.icon-control-play:before{content:"\e071"}.icon-control-pause:before{content:"\e072"}.icon-control-forward:before{content:"\e073"}.icon-control-end:before{content:"\e074"}.icon-volume-1:before{content:"\e09f"}.icon-volume-2:before{content:"\e0a0"}.icon-volume-off:before{content:"\e0a1"}.icon-calendar:before{content:"\e075"}.icon-bulb:before{content:"\e076"}.icon-chart:before{content:"\e077"}.icon-ban:before{content:"\e07c"}.icon-bubble:before{content:"\e07d"}.icon-camrecorder:before{content:"\e07e"}.icon-camera:before{content:"\e07f"}.icon-cloud-download:before{content:"\e083"}.icon-cloud-upload:before{content:"\e084"}.icon-envelope:before{content:"\e086"}.icon-eye:before{content:"\e087"}.icon-flag:before{content:"\e088"}.icon-heart:before{content:"\e08a"}.icon-info:before{content:"\e08b"}.icon-key:before{content:"\e08c"}.icon-link:before{content:"\e08d"}.icon-lock:before{content:"\e08e"}.icon-lock-open:before{content:"\e08f"}.icon-magnifier:before{content:"\e090"}.icon-magnifier-add:before{content:"\e091"}.icon-magnifier-remove:before{content:"\e092"}.icon-paper-clip:before{content:"\e093"}.icon-paper-plane:before{content:"\e094"}.icon-power:before{content:"\e097"}.icon-refresh:before{content:"\e098"}.icon-reload:before{content:"\e099"}.icon-settings:before{content:"\e09a"}.icon-star:before{content:"\e09b"}.icon-symbol-female:before{content:"\e09c"}.icon-symbol-male:before{content:"\e09d"}.icon-target:before{content:"\e09e"}.icon-credit-card:before{content:"\e025"}.icon-paypal:before{content:"\e608"}.icon-social-tumblr:before{content:"\e00a"}.icon-social-twitter:before{content:"\e009"}.icon-social-facebook:before{content:"\e00b"}.icon-social-instagram:before{content:"\e609"}.icon-social-linkedin:before{content:"\e60a"}.icon-social-pinterest:before{content:"\e60b"}.icon-social-github:before{content:"\e60c"}.icon-social-google:before{content:"\e60d"}.icon-social-reddit:before{content:"\e60e"}.icon-social-skype:before{content:"\e60f"}.icon-social-dribbble:before{content:"\e00d"}.icon-social-behance:before{content:"\e610"}.icon-social-foursqare:before{content:"\e611"}.icon-social-soundcloud:before{content:"\e612"}.icon-social-spotify:before{content:"\e613"}.icon-social-stumbleupon:before{content:"\e614"}.icon-social-youtube:before{content:"\e008"}.icon-social-dropbox:before{content:"\e00c"}.icon-social-vkontakte:before{content:"\e618"}.icon-social-steam:before{content:"\e620"}/*# sourceMappingURL=simple-line-icons.min.css.map */ -------------------------------------------------------------------------------- /myproject/core/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 60px; 3 | } 4 | 5 | label.required:after { 6 | content: ' *'; 7 | color: red; 8 | } 9 | 10 | .no { 11 | color: red; 12 | } 13 | -------------------------------------------------------------------------------- /myproject/core/static/fonts/Simple-Line-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/static/fonts/Simple-Line-Icons.eot -------------------------------------------------------------------------------- /myproject/core/static/fonts/Simple-Line-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/static/fonts/Simple-Line-Icons.ttf -------------------------------------------------------------------------------- /myproject/core/static/fonts/Simple-Line-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/static/fonts/Simple-Line-Icons.woff -------------------------------------------------------------------------------- /myproject/core/static/fonts/Simple-Line-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/static/fonts/Simple-Line-Icons.woff2 -------------------------------------------------------------------------------- /myproject/core/static/img/django-logo-negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/core/static/img/django-logo-negative.png -------------------------------------------------------------------------------- /myproject/core/static/js/django-ajax-setup.js: -------------------------------------------------------------------------------- 1 | // set up jQuery ajax object to always send CSRF token in headers 2 | // https://docs.djangoproject.com/en/2.2/ref/csrf/#ajax 3 | var getCookie = function (name) { 4 | var cookieValue = null; 5 | if (document.cookie && document.cookie != '') { 6 | var cookies = document.cookie.split(';'); 7 | for (var i = 0; i < cookies.length; i++) { 8 | var cookie = jQuery.trim(cookies[i]); 9 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 10 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 11 | break; 12 | } 13 | } 14 | } 15 | return cookieValue; 16 | } 17 | 18 | var csrfSafeMethod = function (method) { 19 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 20 | } 21 | 22 | $.ajaxSetup({ 23 | beforeSend: function(xhr, settings) { 24 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 25 | xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /myproject/core/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Django 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% block css %}{% endblock css %} 22 | 23 | 24 | 25 | 26 |
27 | {% include "includes/nav.html" %} 28 | {% block content %}{% endblock content %} 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% block js %}{% endblock js %} 43 | 44 | {% block vuejs %}{% endblock vuejs %} 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /myproject/core/templates/base_login.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block title %}{% endblock title %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% block css %}{% endblock css %} 24 | 25 | 26 | 27 |
28 |
29 | {% block content %}{% endblock content %} 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /myproject/core/templates/includes/nav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /myproject/core/templates/includes/pagination.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 35 |
36 |
-------------------------------------------------------------------------------- /myproject/core/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 | 6 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /myproject/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from myproject.core import views as v 4 | 5 | app_name = 'core' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.index, name='index'), 10 | ] 11 | -------------------------------------------------------------------------------- /myproject/core/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.http import HttpResponse 3 | from django.shortcuts import render 4 | 5 | # @login_required 6 | # def index(request): 7 | # return HttpResponse('

Django

Página simples.

') 8 | 9 | 10 | # @login_required 11 | def index(request): 12 | template_name = 'index.html' 13 | return render(request, template_name) 14 | -------------------------------------------------------------------------------- /myproject/crm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/crm/__init__.py -------------------------------------------------------------------------------- /myproject/crm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Person, Photo 4 | 5 | 6 | @admin.register(Person) 7 | class PersonAdmin(admin.ModelAdmin): 8 | list_display = ('__str__', 'email', 'active') 9 | # readonly_fields = ('slug',) 10 | # list_display_links = ('name',) 11 | search_fields = ('first_name', 'last_name', 'email') 12 | list_filter = ('active',) 13 | # date_hierarchy = 'created' 14 | # ordering = ('-created',) 15 | # actions = ('',) 16 | 17 | 18 | admin.site.register(Photo) 19 | -------------------------------------------------------------------------------- /myproject/crm/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CrmConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'myproject.crm' 7 | -------------------------------------------------------------------------------- /myproject/crm/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Person 4 | 5 | 6 | class PersonForm0(forms.ModelForm): 7 | required_css_class = 'required' 8 | 9 | class Meta: 10 | model = Person 11 | fields = ('first_name', 'last_name') 12 | 13 | 14 | class PersonForm1(forms.ModelForm): 15 | required_css_class = 'required' 16 | 17 | class Meta: 18 | model = Person 19 | fields = ('first_name', 'last_name') 20 | 21 | def __init__(self, *args, **kwargs): 22 | super(PersonForm1, self).__init__(*args, **kwargs) 23 | for field_name, field in self.fields.items(): 24 | field.widget.attrs['class'] = 'form-control' 25 | 26 | 27 | class PersonForm2(forms.ModelForm): 28 | required_css_class = 'required' 29 | 30 | class Meta: 31 | model = Person 32 | fields = ('first_name', 'last_name', 'email') 33 | 34 | def __init__(self, *args, **kwargs): 35 | super(PersonForm2, self).__init__(*args, **kwargs) 36 | for field_name, field in self.fields.items(): 37 | field.widget.attrs['class'] = 'form-control' 38 | 39 | 40 | class PersonForm(forms.ModelForm): 41 | required_css_class = 'required' 42 | 43 | class Meta: 44 | model = Person 45 | # fields = '__all__' 46 | fields = ( 47 | 'first_name', 48 | 'last_name', 49 | 'email', 50 | 'address', 51 | 'address_number', 52 | 'complement', 53 | 'district', 54 | 'city', 55 | 'uf', 56 | 'cep', 57 | 'country', 58 | 'cpf', 59 | 'rg', 60 | 'cnh', 61 | 'active', 62 | ) 63 | 64 | def __init__(self, *args, **kwargs): 65 | super(PersonForm, self).__init__(*args, **kwargs) 66 | for field_name, field in self.fields.items(): 67 | field.widget.attrs['class'] = 'form-control' 68 | self.fields['active'].widget.attrs['class'] = None 69 | 70 | 71 | class ContactForm(forms.Form): 72 | subject = forms.CharField(max_length=100) 73 | message = forms.CharField(widget=forms.Textarea) 74 | sender = forms.EmailField() 75 | cc_myself = forms.BooleanField(required=False) 76 | 77 | 78 | class PersonPhotoForm(forms.ModelForm): 79 | required_css_class = 'required' 80 | # photo = forms.ImageField(required=False) 81 | photo = forms.ImageField( 82 | required=False, 83 | widget=forms.ClearableFileInput(attrs={'multiple': True}) 84 | ) 85 | 86 | class Meta: 87 | model = Person 88 | fields = ('first_name', 'last_name', 'photo') 89 | 90 | def __init__(self, *args, **kwargs): 91 | super(PersonPhotoForm, self).__init__(*args, **kwargs) 92 | for field_name, field in self.fields.items(): 93 | field.widget.attrs['class'] = 'form-control' 94 | self.fields['photo'].widget.attrs['class'] = None 95 | -------------------------------------------------------------------------------- /myproject/crm/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2021-06-06 18:39 2 | 3 | import uuid 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Person', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), 21 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='criado em')), 22 | ('modified', models.DateTimeField(auto_now=True, verbose_name='modificado em')), 23 | ('address', models.CharField(blank=True, max_length=100, null=True, verbose_name='endereço')), 24 | ('address_number', models.IntegerField(blank=True, null=True, verbose_name='número')), 25 | ('complement', models.CharField(blank=True, max_length=100, null=True, verbose_name='complemento')), 26 | ('district', models.CharField(blank=True, max_length=100, null=True, verbose_name='bairro')), 27 | ('city', models.CharField(blank=True, max_length=100, null=True, verbose_name='cidade')), 28 | ('uf', models.CharField(blank=True, choices=[('AC', 'Acre'), ('AL', 'Alagoas'), ('AP', 'Amapá'), ('AM', 'Amazonas'), ('BA', 'Bahia'), ('CE', 'Ceará'), ('DF', 'Distrito Federal'), ('ES', 'Espírito Santo'), ('GO', 'Goiás'), ('MA', 'Maranhão'), ('MT', 'Mato Grosso'), ('MS', 'Mato Grosso do Sul'), ('MG', 'Minas Gerais'), ('PA', 'Pará'), ('PB', 'Paraíba'), ('PR', 'Paraná'), ('PE', 'Pernambuco'), ('PI', 'Piauí'), ('RJ', 'Rio de Janeiro'), ('RN', 'Rio Grande do Norte'), ('RS', 'Rio Grande do Sul'), ('RO', 'Rondônia'), ('RR', 'Roraima'), ('SC', 'Santa Catarina'), ('SP', 'São Paulo'), ('SE', 'Sergipe'), ('TO', 'Tocantins')], max_length=2, null=True, verbose_name='UF')), 29 | ('cep', models.CharField(blank=True, max_length=9, null=True, verbose_name='CEP')), 30 | ('country', models.CharField(blank=True, default='Brasil', max_length=50, null=True, verbose_name='país')), 31 | ('cpf', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='CPF')), 32 | ('rg', models.CharField(blank=True, max_length=11, null=True, verbose_name='RG')), 33 | ('cnh', models.CharField(blank=True, max_length=20, null=True, verbose_name='CNH')), 34 | ('active', models.BooleanField(default=True, verbose_name='ativo')), 35 | ('exist_deleted', models.BooleanField(default=True, help_text='Se for True o item existe. Se for False o item foi deletado.', verbose_name='existe/deletado')), 36 | ('first_name', models.CharField(max_length=50, verbose_name='nome')), 37 | ('last_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='sobrenome')), 38 | ('email', models.EmailField(blank=True, max_length=254, null=True)), 39 | ], 40 | options={ 41 | 'verbose_name': 'pessoa', 42 | 'verbose_name_plural': 'pessoas', 43 | 'ordering': ('first_name',), 44 | }, 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /myproject/crm/migrations/0002_auto_20210606_1948.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2021-06-06 22:48 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('crm', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='person', 16 | name='first_name', 17 | field=models.CharField(help_text='Digite somente o primeiro nome.', max_length=50, verbose_name='nome'), 18 | ), 19 | migrations.CreateModel( 20 | name='Photo', 21 | fields=[ 22 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('photo', models.ImageField(upload_to='', verbose_name='foto')), 24 | ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='crm.person', verbose_name='foto')), 25 | ], 26 | options={ 27 | 'verbose_name': 'foto', 28 | 'verbose_name_plural': 'fotos', 29 | 'ordering': ('pk',), 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /myproject/crm/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/django-forms-tutorial/0288a8c28760e7f1465ba2a2f675245fafed9bf1/myproject/crm/migrations/__init__.py -------------------------------------------------------------------------------- /myproject/crm/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse_lazy 3 | 4 | from myproject.core.models import ( 5 | Active, 6 | Address, 7 | Document, 8 | TimeStampedModel, 9 | UuidModel 10 | ) 11 | 12 | 13 | class Person(UuidModel, TimeStampedModel, Address, Document, Active): 14 | first_name = models.CharField('nome', max_length=50, help_text='Digite somente o primeiro nome.') 15 | last_name = models.CharField('sobrenome', max_length=50, null=True, blank=True) # noqa E501 16 | email = models.EmailField(null=True, blank=True) 17 | 18 | class Meta: 19 | ordering = ('first_name',) 20 | verbose_name = 'pessoa' 21 | verbose_name_plural = 'pessoas' 22 | 23 | @property 24 | def full_name(self): 25 | return f'{self.first_name} {self.last_name or ""}'.strip() 26 | 27 | def __str__(self): 28 | return self.full_name 29 | 30 | def get_absolute_url(self): 31 | return reverse_lazy('crm:person_detail', kwargs={'pk': self.pk}) 32 | 33 | def to_dict(self): 34 | return { 35 | 'id': self.id, 36 | 'first_name': self.first_name, 37 | 'last_name': self.last_name, 38 | 'email': self.email, 39 | } 40 | 41 | 42 | class Photo(models.Model): 43 | photo = models.ImageField('foto', upload_to='') 44 | person = models.ForeignKey( 45 | Person, 46 | on_delete=models.CASCADE, 47 | verbose_name='foto', 48 | related_name='photos', 49 | ) 50 | 51 | class Meta: 52 | ordering = ('pk',) 53 | verbose_name = 'foto' 54 | verbose_name_plural = 'fotos' 55 | 56 | def __str__(self): 57 | return str(self.person) 58 | -------------------------------------------------------------------------------- /myproject/crm/templates/crm/contact_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block title %} 5 | Contact 6 | {% endblock title %} 7 | 8 | {% block content %} 9 | 10 | 16 | 17 |

Send e-mail with Widget Tweaks

18 | 19 |
20 | {% csrf_token %} 21 | {% for field in form.visible_fields %} 22 |
23 | 30 | {% render_field field class="form-control" %} 31 | {% for error in field.errors %} 32 | {{ error }} 33 | {% endfor %} 34 |
35 | {% endfor %} 36 | 37 |
38 | 39 | {% endblock content %} 40 | 41 | {% block js %} 42 | 47 | {% endblock js %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_bootstrap_form.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% load bootstrap4 %} 4 | 5 | {% block content %} 6 |

Bootstrap form

7 |
8 |
9 |
10 | {% csrf_token %} 11 | 12 | {% bootstrap_form form %} 13 | 14 | {% buttons %} 15 | 16 | {% endbuttons %} 17 |
18 |
19 |
20 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_confirm_delete.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Deletar

6 |
7 |
8 |
9 | {% csrf_token %} 10 |

Deseja deletar {{ object }} ?

11 |
12 | 13 | Não 14 |
15 |
16 |
17 |
18 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_crispy_form.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block content %} 6 |

Crispy form

7 |
8 |
9 |
10 | {% csrf_token %} 11 | 12 | {{ form|crispy }} 13 | 14 | 15 |
16 |
17 |
18 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_detail.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Detalhes

6 | 7 | 11 | 12 |
13 | {% for item in object.photos.all %} 14 | {% if item.photo %} 15 | 16 | {% endif %} 17 | {% endfor %} 18 |
19 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_form.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Formulário

6 |
7 |
8 |
9 | {% csrf_token %} 10 | {{ form.as_p }} 11 |
12 | 13 |
14 |
15 |
16 |
17 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_form0.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block css %} 5 | 15 | {% endblock css %} 16 | 17 | {% block content %} 18 |

Formulário

19 |
20 |
21 | {% csrf_token %} 22 |
23 |
24 | 25 | 26 | {{ form.first_name.errors }} 27 |
28 | 29 |
30 | 31 | 32 | {{ form.last_name.errors }} 33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 |
41 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_form1.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block css %} 5 | 15 | {% endblock css %} 16 | 17 | {% block content %} 18 |

Formulário

19 |
20 |
21 | {% csrf_token %} 22 |
23 |
24 | 25 | {{ form.first_name.label_tag }} 26 | {{ form.first_name }} 27 | {{ form.first_name.errors }} 28 |
29 | 30 |
31 | {{ form.last_name.label_tag }} 32 | {{ form.last_name }} 33 | {{ form.last_name.errors }} 34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 |
42 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_form2.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block css %} 5 | 15 | {% endblock css %} 16 | 17 | {% block content %} 18 |

Formulário

19 |
20 |
21 | {% csrf_token %} 22 |
23 | 33 | {{ form.as_p }} 34 |
35 | 36 |
37 |
38 |
39 |
40 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_list.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

6 | Lista 7 | Adicionar 8 | Adicionar com Foto 9 | Adicionar via Ajax 10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for object in object_list %} 21 | 22 | 25 | 26 | 34 | 35 | {% endfor %} 36 | 37 |
NomeE-mailAções
23 | {{ object.full_name }} 24 | {{ object.email|default:'---' }} 27 | 28 | 29 | 30 | 31 | 32 | 33 |
38 | {% include "includes/pagination.html" %} 39 | 40 | {% include "./person_modal.html" %} 41 | {% endblock content %} 42 | 43 | {% block js %} 44 | 45 | 46 | 85 | 86 | {% endblock js %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_modal.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_photo_form.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Formulário com Foto

6 |
7 |
8 |
9 | {% csrf_token %} 10 | {{ form.as_p }} 11 |
12 | 13 |
14 |
15 |
16 |
17 | {% endblock content %} -------------------------------------------------------------------------------- /myproject/crm/templates/crm/person_vuejs_list.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block css %} 5 | 6 | 21 | 22 | {% endblock css %} 23 | 24 | {% block content %} 25 | 26 |
27 |
28 |

Pessoas com VueJS

29 |
30 | 31 |
32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 69 | 70 | 71 |
NomeE-mailAções
62 | ${ person | fullName } 63 | ${ person.email } 66 | 67 | 68 |
72 | 73 | 74 | 106 | 107 |
108 | 109 | {% endblock content %} 110 | 111 | {% block vuejs %} 112 | 113 | 194 | 195 | {% endblock vuejs %} -------------------------------------------------------------------------------- /myproject/crm/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /myproject/crm/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from myproject.crm import views as v 4 | 5 | app_name = 'crm' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.person_list, name='person_list'), 10 | path('/', v.person_detail, name='person_detail'), 11 | path('create/', v.person_create, name='person_create'), 12 | path('/update/', v.person_update, name='person_update'), 13 | path('contact/send/', v.send_contact, name='send_contact'), 14 | path('bootstrap/create/', v.PersonBootstrapCreate.as_view(), name='person_bootstrap_create'), # noqa E501 15 | path('crispy/create/', v.PersonCrispyCreate.as_view(), name='person_crispy_create'), # noqa E501 16 | path('photo/create/', v.photo_create, name='photo_create'), 17 | path('create/ajax/', v.photo_create_ajax, name='photo_create_ajax'), 18 | path('vuejs/', v.person_vuejs_list, name='person_vuejs_list'), 19 | path('vuejs/json/', v.person_json, name='person_json'), 20 | path('vuejs/create/', v.person_vuejs_create, name='person_vuejs_create'), 21 | path('/vuejs/update/', v.person_vuejs_update, name='person_vuejs_update'), # noqa E501 22 | path('/vuejs/delete/', v.person_vuejs_delete, name='person_vuejs_delete'), # noqa E501 23 | ] 24 | -------------------------------------------------------------------------------- /myproject/crm/views.py: -------------------------------------------------------------------------------- 1 | from django.core.mail import send_mail 2 | from django.http import JsonResponse 3 | from django.shortcuts import redirect, render 4 | # from django.urls import reverse_lazy 5 | from django.views.generic import CreateView 6 | 7 | from .forms import ( 8 | ContactForm, 9 | PersonForm, 10 | PersonForm0, 11 | PersonForm1, 12 | PersonForm2, 13 | PersonPhotoForm 14 | ) 15 | from .models import Person, Photo 16 | 17 | 18 | def person_list(request): 19 | template_name = 'crm/person_list.html' 20 | object_list = Person.objects.all() 21 | form = PersonForm1 22 | context = {'object_list': object_list, 'form': form} 23 | return render(request, template_name, context) 24 | 25 | 26 | def person_detail(request, pk): 27 | template_name = 'crm/person_detail.html' 28 | obj = Person.objects.get(pk=pk) 29 | context = {'object': obj} 30 | return render(request, template_name, context) 31 | 32 | 33 | # def person_create(request): 34 | # template_name = 'crm/person_form2.html' 35 | # form = PersonForm1(request.POST or None) 36 | 37 | # if request.method == 'POST': 38 | # if form.is_valid(): 39 | # form.save() 40 | # return redirect('crm:person_list') 41 | 42 | # context = {'form': form} 43 | # return render(request, template_name, context) 44 | 45 | 46 | def person_create(request): 47 | template_name = 'crm/person_form.html' 48 | form = PersonForm(request.POST or None) 49 | 50 | if request.method == 'POST': 51 | if form.is_valid(): 52 | form.save() 53 | return redirect('crm:person_list') 54 | 55 | context = {'form': form} 56 | return render(request, template_name, context) 57 | 58 | 59 | def person_update(request, pk): 60 | template_name = 'crm/person_form.html' 61 | instance = Person.objects.get(pk=pk) 62 | form = PersonForm(request.POST or None, instance=instance) 63 | 64 | if request.method == 'POST': 65 | if form.is_valid(): 66 | form.save() 67 | return redirect('crm:person_list') 68 | 69 | context = {'form': form} 70 | return render(request, template_name, context) 71 | 72 | 73 | def send_contact(request): 74 | template_name = 'crm/contact_form.html' 75 | form = ContactForm(request.POST or None) 76 | 77 | if request.method == 'POST': 78 | if form.is_valid(): 79 | subject = form.cleaned_data.get('subject') 80 | message = form.cleaned_data.get('message') 81 | sender = form.cleaned_data.get('sender') 82 | send_mail( 83 | subject, 84 | message, 85 | sender, 86 | ['localhost'], 87 | fail_silently=False, 88 | ) 89 | return redirect('core:index') 90 | 91 | context = {'form': form} 92 | return render(request, template_name, context) 93 | 94 | 95 | class PersonBootstrapCreate(CreateView): 96 | model = Person 97 | form_class = PersonForm 98 | template_name = 'crm/person_bootstrap_form.html' 99 | 100 | 101 | class PersonCrispyCreate(CreateView): 102 | model = Person 103 | form_class = PersonForm 104 | template_name = 'crm/person_crispy_form.html' 105 | 106 | 107 | # def photo_create(request): 108 | # template_name = 'crm/person_photo_form.html' 109 | # form = PersonPhotoForm(request.POST or None) 110 | 111 | # if request.method == 'POST': 112 | # photo = request.FILES.get('photo') # pega apenas um arquivo. 113 | # if form.is_valid(): 114 | # person = form.save() 115 | # Photo.objects.create(person=person, photo=photo) 116 | # return redirect('crm:person_detail', person.pk) 117 | 118 | # context = {'form': form} 119 | # return render(request, template_name, context) 120 | 121 | def photo_create(request): 122 | template_name = 'crm/person_photo_form.html' 123 | form = PersonPhotoForm(request.POST or None) 124 | 125 | if request.method == 'POST': 126 | photos = request.FILES.getlist('photo') # pega vários arquivos. 127 | 128 | if form.is_valid(): 129 | person = form.save() 130 | 131 | for photo in photos: 132 | Photo.objects.create(person=person, photo=photo) 133 | 134 | return redirect('crm:person_detail', person.pk) 135 | 136 | context = {'form': form} 137 | return render(request, template_name, context) 138 | 139 | 140 | def photo_create_ajax(request): 141 | form = PersonForm1(request.POST or None) 142 | 143 | if request.method == 'POST': 144 | if form.is_valid(): 145 | person = form.save() 146 | data = [person.to_dict()] 147 | return JsonResponse({'data': data}) 148 | 149 | 150 | def person_vuejs_list(request): 151 | # Renderiza a página 152 | template_name = 'crm/person_vuejs_list.html' 153 | return render(request, template_name) 154 | 155 | 156 | def person_json(request): 157 | # Retorna os dados 158 | persons = Person.objects.all() 159 | data = [person.to_dict() for person in persons] 160 | return JsonResponse({'data': data}) 161 | 162 | 163 | def person_vuejs_create(request): 164 | # Salva os dados 165 | form = PersonForm1(request.POST or None) 166 | 167 | if request.method == 'POST': 168 | if form.is_valid(): 169 | person = form.save() 170 | data = person.to_dict() 171 | return JsonResponse({'data': data}) 172 | 173 | 174 | def person_vuejs_update(request, pk): 175 | person = Person.objects.get(pk=pk) 176 | form = PersonForm2(request.POST or None, instance=person) 177 | 178 | if request.method == 'POST': 179 | if form.is_valid(): 180 | person = form.save() 181 | data = person.to_dict() 182 | return JsonResponse({'data': data}) 183 | 184 | 185 | def person_vuejs_delete(request, pk): 186 | if request.method == 'DELETE': 187 | person = Person.objects.get(pk=pk) 188 | person.delete() 189 | return JsonResponse({'status': 204}) 190 | -------------------------------------------------------------------------------- /myproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for myproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | from decouple import Csv, config 16 | from dj_database_url import parse as dburl 17 | 18 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 19 | BASE_DIR = Path(__file__).resolve().parent.parent 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = config('SECRET_KEY') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = config('DEBUG', default=False, cast=bool) 30 | 31 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | # thirty apps 44 | 'django_extensions', 45 | 'widget_tweaks', 46 | 'bootstrap4', 47 | 'crispy_forms', 48 | # my apps 49 | 'myproject.accounts', 50 | 'myproject.core', 51 | 'myproject.crm', 52 | ] 53 | 54 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 55 | 56 | MIDDLEWARE = [ 57 | 'django.middleware.security.SecurityMiddleware', 58 | 'django.contrib.sessions.middleware.SessionMiddleware', 59 | 'django.middleware.common.CommonMiddleware', 60 | 'django.middleware.csrf.CsrfViewMiddleware', 61 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 62 | 'django.contrib.messages.middleware.MessageMiddleware', 63 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 64 | ] 65 | 66 | ROOT_URLCONF = 'myproject.urls' 67 | 68 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 69 | 70 | DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', 'webmaster@localhost') 71 | EMAIL_HOST = config('EMAIL_HOST', '0.0.0.0') # localhost 72 | EMAIL_PORT = config('EMAIL_PORT', 1025, cast=int) 73 | EMAIL_HOST_USER = config('EMAIL_HOST_USER', '') 74 | EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', '') 75 | EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) 76 | 77 | TEMPLATES = [ 78 | { 79 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 80 | 'DIRS': [], 81 | 'APP_DIRS': True, 82 | 'OPTIONS': { 83 | 'context_processors': [ 84 | 'django.template.context_processors.debug', 85 | 'django.template.context_processors.request', 86 | 'django.contrib.auth.context_processors.auth', 87 | 'django.contrib.messages.context_processors.messages', 88 | ], 89 | }, 90 | }, 91 | ] 92 | 93 | WSGI_APPLICATION = 'myproject.wsgi.application' 94 | 95 | 96 | # Database 97 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 98 | 99 | default_dburl = 'sqlite:///' + str(BASE_DIR / 'db.sqlite3') 100 | DATABASES = { 101 | 'default': config('DATABASE_URL', default=default_dburl, cast=dburl), 102 | } 103 | 104 | 105 | # Password validation 106 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 107 | 108 | AUTH_PASSWORD_VALIDATORS = [ 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 117 | }, 118 | { 119 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 120 | }, 121 | ] 122 | 123 | 124 | # Internationalization 125 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 126 | 127 | LANGUAGE_CODE = 'pt-br' # 'en-us' 128 | 129 | TIME_ZONE = 'America/Sao_Paulo' # 'UTC' 130 | 131 | USE_I18N = True 132 | 133 | USE_L10N = True 134 | 135 | USE_TZ = True 136 | 137 | USE_THOUSAND_SEPARATOR = True 138 | 139 | DECIMAL_SEPARATOR = ',' 140 | 141 | 142 | # Static files (CSS, JavaScript, Images) 143 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 144 | 145 | STATIC_URL = '/static/' 146 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles') 147 | 148 | MEDIA_URL = '/media/' 149 | MEDIA_ROOT = BASE_DIR.joinpath('media') 150 | 151 | # Default primary key field type 152 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 153 | 154 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 155 | 156 | 157 | LOGIN_URL = '/admin/login/' 158 | LOGIN_REDIRECT_URL = 'core:index' 159 | # LOGOUT_REDIRECT_URL = 'core:index' 160 | -------------------------------------------------------------------------------- /myproject/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import include, path 5 | 6 | urlpatterns = [ 7 | path('', include('myproject.core.urls', namespace='core')), 8 | path('accounts/', include('myproject.accounts.urls')), # without namespace 9 | path('crm/', include('myproject.crm.urls', namespace='crm')), 10 | path('admin/', admin.site.urls), 11 | ] 12 | 13 | if settings.DEBUG: 14 | urlpatterns += static( 15 | settings.MEDIA_URL, 16 | document_root=settings.MEDIA_ROOT, 17 | ) 18 | -------------------------------------------------------------------------------- /myproject/utils/progress_bar.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def progressbar(it, prefix="", size=60, file=sys.stdout): 5 | count = len(it) 6 | 7 | def show(j): 8 | x = int(size * j / count) 9 | file.write("%s[%s%s] %i/%i\r" % 10 | (prefix, "#" * x, "." * (size - x), j, count)) 11 | file.flush() 12 | show(0) 13 | for i, item in enumerate(it): 14 | yield item 15 | show(i + 1) 16 | file.write("\n") 17 | file.flush() 18 | -------------------------------------------------------------------------------- /myproject/utils/utils.py: -------------------------------------------------------------------------------- 1 | import string 2 | from datetime import date, datetime, timedelta 3 | from random import choice, random, randrange 4 | 5 | from django.utils.text import slugify 6 | from faker import Faker 7 | 8 | fake = Faker() 9 | 10 | 11 | def gen_string(max_length): 12 | return str(''.join(choice(string.ascii_letters) for i in range(max_length))) 13 | 14 | 15 | gen_string.required = ['max_length'] 16 | 17 | 18 | def gen_digits(max_length: int): 19 | '''Gera dígitos numéricos.''' 20 | return str(''.join(choice(string.digits) for i in range(max_length))) 21 | 22 | 23 | def gen_first_name(): 24 | return fake.first_name() 25 | 26 | 27 | def gen_last_name(): 28 | return fake.last_name() 29 | 30 | 31 | def gen_email(first_name: str, last_name: str, company: str = None): 32 | first_name = slugify(first_name) 33 | last_name = slugify(last_name) 34 | email = f'{first_name}.{last_name}@email.com' 35 | return email 36 | 37 | 38 | def gen_date(min_year=2019, max_year=datetime.now().year): 39 | # gera um date no formato yyyy-mm-dd 40 | start = date(min_year, 1, 1) 41 | years = max_year - min_year + 1 42 | end = start + timedelta(days=365 * years) 43 | return start + (end - start) * random() 44 | 45 | 46 | def gen_rg(): 47 | return gen_digits(10) 48 | 49 | 50 | def gen_cpf(): 51 | def calcula_digito(digs): 52 | s = 0 53 | qtd = len(digs) 54 | for i in range(qtd): 55 | s += n[i] * (1 + qtd - i) 56 | res = 11 - s % 11 57 | if res >= 10: 58 | return 0 59 | return res 60 | n = [randrange(10) for i in range(9)] 61 | n.append(calcula_digito(n)) 62 | n.append(calcula_digito(n)) 63 | return "%d%d%d%d%d%d%d%d%d%d%d" % tuple(n) 64 | 65 | 66 | def gen_phone(): 67 | return f'{gen_digits(2)} {gen_digits(4)}-{gen_digits(4)}' 68 | 69 | 70 | def gen_text(): 71 | return fake.paragraph(nb_sentences=5) 72 | -------------------------------------------------------------------------------- /myproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for myproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /passo-a-passo.md: -------------------------------------------------------------------------------- 1 | # Passo a passo 2 | 3 | # django-forms-tutorial 4 | 5 | ![1400605](img/1400605.jpg) 6 | 7 | ![dr_strange_failure](img/dr_strange_failure.gif) 8 | 9 | 10 | 11 | ## Criando projeto com boilerplate 12 | 13 | ``` 14 | git clone https://github.com/rg3915/django-boilerplate.git /tmp/django-boilerplate 15 | cp /tmp/django-boilerplate/boilerplatesimple.sh . 16 | source boilerplatesimple.sh 17 | ``` 18 | 19 | 20 | ## Como o Django funciona? 21 | 22 | ![mtv1](img/mtv1.png) 23 | 24 | --- 25 | 26 | ![mtv3](img/mtv3.png) 27 | 28 | --- 29 | 30 | ## 1 - Admin - Login 31 | 32 | ![login.png](img/login.png) 33 | 34 | ```html 35 |
36 | 37 |
38 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 |
48 | 49 |
50 | 51 |
52 |
53 | ``` 54 | 55 | https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/login.html#L44-L63 56 | 57 | --- 58 | 59 | ## 2 - [Building a form](https://docs.djangoproject.com/en/3.2/topics/forms/#building-a-form) 60 | 61 | https://docs.djangoproject.com/en/3.2/topics/forms/#building-a-form 62 | 63 | ```html 64 |
65 | 66 | 67 | 68 |
69 | ``` 70 | 71 | No nosso projeto: 72 | 73 | Edite `crm/urls.py` 74 | 75 | ```python 76 | urlpatterns = [ 77 | path('', v.person_list, name='person_list'), 78 | path('/', v.person_detail, name='person_detail'), 79 | path('create/', v.person_create, name='person_create'), 80 | ] 81 | ``` 82 | 83 | Edite `crm/views.py` 84 | 85 | ```python 86 | from django.shortcuts import redirect, render 87 | 88 | from .models import Person 89 | 90 | 91 | def person_list(request): 92 | template_name = 'crm/person_list.html' 93 | object_list = Person.objects.all() 94 | context = {'object_list': object_list} 95 | return render(request, template_name, context) 96 | 97 | 98 | def person_detail(request, pk): 99 | template_name = 'crm/person_detail.html' 100 | obj = Person.objects.get(pk=pk) 101 | context = {'object': obj} 102 | return render(request, template_name, context) 103 | 104 | 105 | def person_create(request): 106 | template_name = 'crm/person_form0.html' 107 | # import ipdb; ipdb.set_trace() 108 | return render(request, template_name) 109 | ``` 110 | 111 | ```html 112 | 113 | {% extends "base.html" %} 114 | 115 | {% block content %} 116 |

Formulário

117 |
118 |
119 |
120 |
121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 | 130 |
131 | 132 |
133 |
134 |
135 |
136 | {% endblock content %} 137 | ``` 138 | 139 | Complemente o HTML com as classes. 140 | 141 | ```html 142 | 143 | {% extends "base.html" %} 144 | 145 | {% block css %} 146 | 152 | {% endblock css %} 153 | 154 | {% block content %} 155 |

Formulário

156 |
157 |
158 |
159 |
160 | 161 | 162 |
163 | 164 |
165 | 166 | 167 |
168 | 169 |
170 | 171 |
172 |
173 |
174 |
175 | {% endblock content %} 176 | ``` 177 | 178 | Em `crm/views.py` 179 | 180 | Rodando a aplicação com ipdb, vemos que precisamos do 181 | 182 | ```html 183 | {% csrf_token %} 184 | ``` 185 | 186 | E continuando o debug, precisamos separar GET de POST. 187 | 188 | ```python 189 | def person_create(request): 190 | template_name = 'crm/person_form0.html' 191 | if request.method == 'GET': 192 | print('GET') 193 | else: 194 | print('POST') 195 | return render(request, template_name) 196 | ``` 197 | 198 | E depois pegamos os valores dos inputs com 199 | 200 | ```python 201 | ... 202 | first_name = request.POST.get('first_name') 203 | last_name = request.POST.get('last_name') 204 | print(first_name, last_name) 205 | ``` 206 | 207 | Então podemos salvar os dados da seguinte forma: 208 | 209 | ```python 210 | def person_create(request): 211 | template_name = 'crm/person_form0.html' 212 | if request.method == 'GET': 213 | print('GET') 214 | else: 215 | print('POST') 216 | 217 | first_name = request.POST.get('first_name') 218 | last_name = request.POST.get('last_name') 219 | print(first_name, last_name) 220 | 221 | Person.objects.create(first_name=first_name, last_name=last_name) 222 | 223 | return render(request, template_name) 224 | ``` 225 | 226 | Refatorando o código 227 | 228 | ```python 229 | def person_create(request): 230 | template_name = 'crm/person_form0.html' 231 | 232 | if request.method == 'POST': 233 | first_name = request.POST.get('first_name') 234 | last_name = request.POST.get('last_name') 235 | 236 | Person.objects.create(first_name=first_name, last_name=last_name) 237 | 238 | return redirect('crm:person_list') 239 | 240 | return render(request, template_name) 241 | ``` 242 | 243 | Edite `crm/forms.py` 244 | 245 | ```python 246 | class PersonForm0(forms.ModelForm): 247 | required_css_class = 'required' 248 | 249 | class Meta: 250 | model = Person 251 | fields = ('first_name', 'last_name') 252 | ``` 253 | 254 | Edite `crm/views.py` 255 | 256 | ```python 257 | from .forms import PersonForm0 258 | 259 | 260 | def person_create(request): 261 | template_name = 'crm/person_form0.html' 262 | form = PersonForm0(request.POST or None) 263 | 264 | if request.method == 'POST': 265 | if form.is_valid(): 266 | form.save() 267 | return redirect('crm:person_list') 268 | else: 269 | print(form.errors) 270 | 271 | context = {'form': form} 272 | return render(request, template_name, context) 273 | ``` 274 | 275 | Acrescente valores inválidos, como `first_name` vazio ou texto muito longo. 276 | 277 | ```html 278 | ... 279 | .errorlist { 280 | list-style: none; 281 | color: red; 282 | } 283 | ... 284 | {{ form.first_name.errors }} 285 | ... 286 | {{ form.last_name.errors }} 287 | ... 288 | ``` 289 | 290 | ### form 1 291 | 292 | Agora vamos renderizar todos os campos do formulário na mão. 293 | 294 | https://docs.djangoproject.com/en/3.2/topics/forms/#rendering-fields-manually 295 | 296 | Edite `crm/views.py` 297 | 298 | ```python 299 | def person_create(request): 300 | template_name = 'crm/person_form1.html' 301 | form = PersonForm0(request.POST or None) 302 | 303 | if request.method == 'POST': 304 | if form.is_valid(): 305 | form.save() 306 | return redirect('crm:person_list') 307 | 308 | context = {'form': form} 309 | return render(request, template_name, context) 310 | ``` 311 | 312 | ```html 313 | ... 314 |
315 | 316 | {{ form.first_name.label_tag }} 317 | {{ form.first_name }} 318 | {{ form.first_name.errors }} 319 |
320 | 321 |
322 | {{ form.last_name.label_tag }} 323 | {{ form.last_name }} 324 | {{ form.last_name.errors }} 325 |
326 | ... 327 | ``` 328 | 329 | Edite `crm/forms.py` 330 | 331 | ```python 332 | class PersonForm1(forms.ModelForm): 333 | required_css_class = 'required' 334 | 335 | class Meta: 336 | model = Person 337 | fields = ('first_name', 'last_name') 338 | 339 | def __init__(self, *args, **kwargs): 340 | super(PersonForm1, self).__init__(*args, **kwargs) 341 | for field_name, field in self.fields.items(): 342 | field.widget.attrs['class'] = 'form-control' 343 | ``` 344 | 345 | Renderizando todos os campos com loop. 346 | 347 | ```html 348 | ... 349 | {% for field in form %} 350 |
351 | {{ field.errors }} 352 | {{ field.label_tag }} 353 | {{ field }} 354 | {% if field.help_text %} 355 | {{ field.help_text|safe }} 356 | {% endif %} 357 |
358 | {% endfor %} 359 | ... 360 | ``` 361 | 362 | Ou simplesmente 363 | 364 | ```html 365 | {{ form.as_p }} 366 | ``` 367 | 368 | remova o estilo de `.required:after` 369 | 370 | E finalmente o `PersonForm` completo 371 | 372 | ```python 373 | # crm/views.py 374 | def person_create(request): 375 | template_name = 'crm/person_form.html' 376 | form = PersonForm(request.POST or None) 377 | 378 | if request.method == 'POST': 379 | if form.is_valid(): 380 | form.save() 381 | return redirect('crm:person_list') 382 | 383 | context = {'form': form} 384 | return render(request, template_name, context) 385 | ``` 386 | 387 | ```python 388 | # crm/forms.py 389 | class PersonForm(forms.ModelForm): 390 | required_css_class = 'required' 391 | 392 | class Meta: 393 | model = Person 394 | # fields = '__all__' 395 | fields = ( 396 | 'first_name', 397 | 'last_name', 398 | 'email', 399 | 'address', 400 | 'address_number', 401 | 'complement', 402 | 'district', 403 | 'city', 404 | 'uf', 405 | 'cep', 406 | 'country', 407 | 'cpf', 408 | 'rg', 409 | 'cnh', 410 | 'active', 411 | ) 412 | 413 | def __init__(self, *args, **kwargs): 414 | super(PersonForm, self).__init__(*args, **kwargs) 415 | for field_name, field in self.fields.items(): 416 | field.widget.attrs['class'] = 'form-control' 417 | self.fields['active'].widget.attrs['class'] = None 418 | ``` 419 | 420 | Mostrar `person_form.html` pronto. 421 | 422 | 423 | ### Editar 424 | 425 | Edite `crm/urls.py` 426 | 427 | ```python 428 | ... 429 | path('/update', v.person_update, name='person_update'), 430 | ... 431 | ``` 432 | 433 | Edite `crm/views.py` 434 | 435 | ```python 436 | def person_update(request, pk): 437 | template_name = 'crm/person_form.html' 438 | instance = Person.objects.get(pk=pk) 439 | form = PersonForm(request.POST or None, instance=instance) 440 | 441 | if request.method == 'POST': 442 | if form.is_valid(): 443 | form.save() 444 | return redirect('crm:person_list') 445 | 446 | context = {'form': form} 447 | return render(request, template_name, context) 448 | ``` 449 | 450 | 451 | ## 3 - Tela de Contato com forms.py e Django Widget Tweaks 452 | 453 | https://pypi.org/project/django-widget-tweaks/ 454 | 455 | ``` 456 | pip install django-widget-tweaks 457 | ``` 458 | 459 | Editar `settings.py` 460 | 461 | ```python 462 | INSTALLED_APPS = [ 463 | ... 464 | 'widget_tweaks', 465 | ... 466 | ] 467 | ``` 468 | 469 | ```python 470 | # crm/forms.py 471 | class ContactForm(forms.Form): 472 | subject = forms.CharField(max_length=100) 473 | message = forms.CharField(widget=forms.Textarea) 474 | sender = forms.EmailField() 475 | cc_myself = forms.BooleanField(required=False) 476 | ``` 477 | 478 | 479 | 480 | ```html 481 | 482 | {% extends "base.html" %} 483 | {% load widget_tweaks %} 484 | 485 | {% block title %} 486 | Contact 487 | {% endblock title %} 488 | 489 | {% block content %} 490 | 491 | 497 | 498 |

Send e-mail with Widget Tweaks

499 |
500 | {% csrf_token %} 501 | {% for field in form.visible_fields %} 502 |
503 | 510 | {% render_field field class="form-control" %} 511 | {% for error in field.errors %} 512 | {{ error }} 513 | {% endfor %} 514 |
515 | {% endfor %} 516 | 517 |
518 | 519 | {% endblock content %} 520 | 521 | {% block js %} 522 | 527 | {% endblock js %} 528 | ``` 529 | 530 | ![band_contact](img/band_contact.png) 531 | 532 | Editar `crm/urls.py` 533 | 534 | ```python 535 | path('contact/send/', v.send_contact, name='send_contact'), 536 | ``` 537 | 538 | Editar `crm/views.py` 539 | 540 | ```python 541 | from django.core.mail import send_mail 542 | 543 | 544 | def send_contact(request): 545 | template_name = 'crm/contact_form.html' 546 | form = ContactForm(request.POST or None) 547 | 548 | if request.method == 'POST': 549 | if form.is_valid(): 550 | subject = form.cleaned_data.get('subject') 551 | message = form.cleaned_data.get('message') 552 | sender = form.cleaned_data.get('sender') 553 | send_mail( 554 | subject, 555 | message, 556 | sender, 557 | ['localhost'], 558 | fail_silently=False, 559 | ) 560 | return redirect('core:index') 561 | 562 | context = {'form': form} 563 | return render(request, template_name, context) 564 | ``` 565 | 566 | Editar `nav.html` 567 | 568 | ```html 569 | 572 | ``` 573 | 574 | Editar `base.html` 575 | 576 | ```html 577 | {% block js %}{% endblock js %} 578 | ``` 579 | 580 | Enviar o e-mail com MailHog. 581 | 582 | ``` 583 | docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog 584 | ``` 585 | 586 | Configurar `settings.py` 587 | 588 | ``` 589 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 590 | 591 | DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', 'webmaster@localhost') 592 | EMAIL_HOST = config('EMAIL_HOST', '0.0.0.0') # localhost 593 | EMAIL_PORT = config('EMAIL_PORT', 1025, cast=int) 594 | EMAIL_HOST_USER = config('EMAIL_HOST_USER', '') 595 | EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', '') 596 | EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) 597 | ``` 598 | 599 | 600 | 601 | ## 4 - Django Bootstrap 602 | 603 | https://github.com/zostera/django-bootstrap4 604 | 605 | ``` 606 | pip install django-bootstrap4 607 | ``` 608 | 609 | ```python 610 | # settings.py 611 | INSTALLED_APPS = [ 612 | ... 613 | 'bootstrap4', 614 | ``` 615 | 616 | https://getbootstrap.com/ 617 | 618 | Edite `base.html` 619 | 620 | ```html 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | ``` 631 | 632 | 633 | Edite `person_bootstrap_form.html` 634 | 635 | ```html 636 | 637 | {% extends "base.html" %} 638 | {% load bootstrap4 %} 639 | 640 | {% block content %} 641 |

Bootstrap form

642 | 643 |
644 | {% csrf_token %} 645 | 646 | {% bootstrap_form form %} 647 | 648 | {% buttons %} 649 | 650 | {% endbuttons %} 651 |
652 | 653 | {% endblock content %} 654 | ``` 655 | 656 | Editar `nav.html` 657 | 658 | ```html 659 | 662 | ``` 663 | 664 | Edite `crm/urls.py` 665 | 666 | ```python 667 | path('bootstrap/create/', v.PersonBootstrapCreate.as_view(), name='person_bootstrap_create'), 668 | ``` 669 | 670 | Edite `crm/views.py` 671 | 672 | ```python 673 | from django.views.generic import CreateView 674 | 675 | 676 | class PersonBootstrapCreate(CreateView): 677 | model = Person 678 | form_class = PersonForm 679 | template_name = 'crm/person_bootstrap_form.html' 680 | ``` 681 | 682 | Leia [django-cbv-tutorial](https://github.com/rg3915/django-cbv-tutorial) 683 | 684 | E veja a Live [Django Class Based View como você nunca viu](https://youtu.be/C7Ecugxa7ic) 685 | 686 | 687 | 688 | ## 5 - Django Crispy Forms 689 | 690 | https://django-crispy-forms.readthedocs.io/en/latest/ 691 | 692 | 693 | ``` 694 | pip install django-crispy-forms 695 | ``` 696 | 697 | https://simpleisbetterthancomplex.com/tutorial/2018/11/28/advanced-form-rendering-with-django-crispy-forms.html 698 | 699 | ```python 700 | # settings.py 701 | INSTALLED_APPS = [ 702 | ... 703 | 'crispy_forms', 704 | ... 705 | ] 706 | 707 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 708 | ``` 709 | 710 | ```html 711 | 712 | {% extends "base.html" %} 713 | {% load crispy_forms_tags %} 714 | 715 | {% block content %} 716 | 717 |
718 | {% csrf_token %} 719 | 720 | {{ form|crispy }} 721 | 722 | 723 |
724 | 725 | {% endblock content %} 726 | ``` 727 | 728 | Editar `nav.html` 729 | 730 | ```html 731 | 734 | ``` 735 | 736 | Editar `crm/urls.py` 737 | 738 | ```python 739 | path('crispy/create/', v.PersonCrispyCreate.as_view(), name='person_crispy_create'), 740 | ``` 741 | 742 | Editar `crm/views.py` 743 | 744 | ```python 745 | class PersonCrispyCreate(CreateView): 746 | model = Person 747 | form_class = PersonForm 748 | template_name = 'crm/person_crispy_form.html' 749 | ``` 750 | 751 | 752 | 753 | ## 6 - Upload File 754 | 755 | 756 | YouTube: https://youtu.be/yCh7iINWMRs 757 | 758 | Github: https://github.com/rg3915/gallery 759 | 760 | 761 | Editar `settings.py` 762 | 763 | ```python 764 | # settings.py 765 | ... 766 | MEDIA_URL = '/media/' 767 | MEDIA_ROOT = BASE_DIR.joinpath('media') 768 | ... 769 | ``` 770 | 771 | 772 | Editar `urls.py` 773 | 774 | ```python 775 | # urls.py 776 | from django.conf import settings 777 | from django.conf.urls.static import static 778 | 779 | ... 780 | 781 | if settings.DEBUG: 782 | urlpatterns += static( 783 | settings.MEDIA_URL, 784 | document_root=settings.MEDIA_ROOT, 785 | ) 786 | ``` 787 | 788 | 789 | Editar `crm/models.py` 790 | 791 | ```python 792 | class Photo(models.Model): 793 | photo = models.ImageField('foto', upload_to='') 794 | person = models.ForeignKey( 795 | Person, 796 | on_delete=models.CASCADE, 797 | verbose_name='foto', 798 | related_name='photos', 799 | ) 800 | 801 | class Meta: 802 | ordering = ('pk',) 803 | verbose_name = 'foto' 804 | verbose_name_plural = 'fotos' 805 | 806 | def __str__(self): 807 | return str(self.person) 808 | ``` 809 | 810 | 811 | Editar `crm/admin.py` 812 | 813 | ```python 814 | admin.site.register(Photo) 815 | ``` 816 | 817 | Editar `crm/urls.py` 818 | 819 | ```python 820 | path('photo/create/', v.photo_create, name='photo_create'), 821 | ``` 822 | 823 | Editar `person_list.html` 824 | 825 | ```html 826 | Adicionar com Foto 827 | ``` 828 | 829 | Editar `person_detail.html` 830 | 831 | ```html 832 |
833 | {% for item in object.photos.all %} 834 | {% if item.photo %} 835 | 836 | {% endif %} 837 | {% endfor %} 838 |
839 | ``` 840 | 841 | Editar `person_photo_form.html` 842 | 843 | ```html 844 | 845 | {% extends "base.html" %} 846 | 847 | {% block content %} 848 |

Formulário com Foto

849 |
850 |
851 |
852 | {% csrf_token %} 853 | {{ form.as_p }} 854 |
855 | 856 |
857 |
858 |
859 |
860 | {% endblock content %} 861 | ``` 862 | 863 | Editar `crm/forms.py` 864 | 865 | ```python 866 | class PersonPhotoForm(forms.ModelForm): 867 | required_css_class = 'required' 868 | photo = forms.ImageField(required=False) 869 | # photo = forms.FileField( 870 | # required=False, 871 | # widget=forms.ClearableFileInput(attrs={'multiple': True}) 872 | # ) 873 | 874 | class Meta: 875 | model = Person 876 | fields = ('first_name', 'last_name', 'photo') 877 | 878 | def __init__(self, *args, **kwargs): 879 | super(PersonPhotoForm, self).__init__(*args, **kwargs) 880 | for field_name, field in self.fields.items(): 881 | field.widget.attrs['class'] = 'form-control' 882 | self.fields['photo'].widget.attrs['class'] = None 883 | ``` 884 | 885 | Editar `crm/views.py` para um arquivo 886 | 887 | ```python 888 | def photo_create(request): 889 | template_name = 'crm/person_photo_form.html' 890 | form = PersonPhotoForm(request.POST or None) 891 | if request.method == 'POST': 892 | photo = request.FILES.get('photo') 893 | if form.is_valid(): 894 | person = form.save() 895 | Photo.objects.create(person=person, photo=photo) 896 | return redirect('crm:person_detail', person.pk) 897 | context = {'form': form} 898 | return render(request, template_name, context) 899 | ``` 900 | 901 | Editar `crm/views.py` para vários arquivos 902 | 903 | ```python 904 | def photo_create(request): 905 | template_name = 'crm/person_photo_form.html' 906 | form = PersonPhotoForm(request.POST or None) 907 | 908 | if request.method == 'POST': 909 | photos = request.FILES.getlist('photo') 910 | 911 | if form.is_valid(): 912 | person = form.save() 913 | 914 | for photo in photos: 915 | Photo.objects.create(person=person, photo=photo) 916 | 917 | return redirect('crm:person_detail', person.pk) 918 | 919 | context = {'form': form} 920 | return render(request, template_name, context) 921 | ``` 922 | 923 | 924 | 925 | ## 7 - POST via Ajax (Live Code) 926 | 927 | 1. Requer jQuery 928 | 2. Criar formulário num Modal 929 | 3. Criar uma url para fazer o Post 930 | 4. Criar View que salva os dados 931 | 5. Fazer o Post via Ajax 932 | 6. Retornar os novos dados na tabela 933 | 934 | 935 | 1. Requer jQuery 936 | 937 | ```html 938 | 939 | 940 | ``` 941 | 942 | 2. Criar formulário com Modal 943 | 944 | ```html 945 | 946 | 968 | ``` 969 | 970 | 971 | Editar `crm/person_list.html` 972 | 973 | ```html 974 | Adicionar via Ajax 975 | ... 976 | {% include "./person_modal.html" %} 977 | ``` 978 | 979 | 980 | Editar crm/views.py 981 | 982 | ```python 983 | def person_list(request): 984 | template_name = 'crm/person_list.html' 985 | object_list = Person.objects.all() 986 | form = PersonForm1 987 | context = {'object_list': object_list, 'form': form} 988 | return render(request, template_name, context) 989 | ``` 990 | 991 | 992 | 993 | 3. Criar uma url para fazer o Post 994 | 995 | ```python 996 | # urls.py 997 | path('create/ajax/', v.person_create_ajax, name='person_create_ajax'), 998 | ``` 999 | 1000 | 1001 | 1002 | 4. Criar View que salva os dados 1003 | 1004 | Editar crm/views.py 1005 | 1006 | 1007 | ```python 1008 | from django.http import JsonResponse 1009 | 1010 | 1011 | def person_create_ajax(request): 1012 | form = PersonForm1(request.POST or None) 1013 | if request.method == 'POST': 1014 | if form.is_valid(): 1015 | person = form.save() 1016 | data = [person.to_dict()] 1017 | return JsonResponse({'data': data}) 1018 | ``` 1019 | 1020 | 1021 | Editar `crm/models.py` 1022 | 1023 | ```python 1024 | def to_dict(self): 1025 | return { 1026 | 'id': self.id, 1027 | 'first_name': self.first_name, 1028 | 'last_name': self.last_name, 1029 | 'email': self.email, 1030 | } 1031 | ``` 1032 | 1033 | 1034 | 1035 | 1036 | 5. Fazer o Post via Ajax 1037 | 1038 | Requer `django-ajax-setup.js` 1039 | 1040 | ``` 1041 | mkdir myproject/core/static/js 1042 | touch myproject/core/static/js/django-ajax-setup.js 1043 | ``` 1044 | 1045 | https://docs.djangoproject.com/en/3.2/ref/csrf/#ajax 1046 | 1047 | ```js 1048 | // set up jQuery ajax object to always send CSRF token in headers 1049 | // https://docs.djangoproject.com/en/2.2/ref/csrf/#ajax 1050 | var getCookie = function (name) { 1051 | var cookieValue = null; 1052 | if (document.cookie && document.cookie != '') { 1053 | var cookies = document.cookie.split(';'); 1054 | for (var i = 0; i < cookies.length; i++) { 1055 | var cookie = jQuery.trim(cookies[i]); 1056 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 1057 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 1058 | break; 1059 | } 1060 | } 1061 | } 1062 | return cookieValue; 1063 | } 1064 | 1065 | var csrfSafeMethod = function (method) { 1066 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 1067 | } 1068 | 1069 | $.ajaxSetup({ 1070 | beforeSend: function(xhr, settings) { 1071 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 1072 | xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); 1073 | } 1074 | } 1075 | }); 1076 | ``` 1077 | 1078 | Editar `crm/person_list.html` 1079 | 1080 | 1081 | ```js 1082 | {% block js %} 1083 | 1084 | 1107 | 1108 | {% endblock js %} 1109 | ``` 1110 | 1111 | 1112 | 1113 | 1114 | 6. Retornar os novos dados na tabela 1115 | 1116 | ```js 1117 | function addItem(response) { 1118 | const data = response.data[0]; 1119 | const template = '' + 1120 | '' + data.first_name + ' ' + data.last_name + '' + 1121 | '' + data.email + '' + 1122 | '' 1123 | 1124 | $('table tbody').append(template) 1125 | }; 1126 | 1127 | function closeModal() { 1128 | $('#myModal').modal('hide'); 1129 | // Limpa os campos 1130 | $('#id_first_name').val(''); 1131 | $('#id_last_name').val(''); 1132 | $('#id_email').val(''); 1133 | } 1134 | ``` 1135 | 1136 | **Atenção:** Não esquecer de chamar a função em `success` e em `complete`. 1137 | 1138 | 1139 | 1140 | 1141 | ## 8 - POST com VueJS 1142 | 1143 | 1. Requer VueJS + Axios 1144 | 2. Criar um template com função 1145 | 3. Editar views.py 1146 | 4. Fazer o Post via Axios 1147 | 1148 | 1149 | 1. Requer VueJS + Axios 1150 | 1151 | Editar `base.html` 1152 | 1153 | ``` 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | {% block vuejs %}{% endblock vuejs %} 1160 | ``` 1161 | 1162 | Editar `nav.html` 1163 | 1164 | ```html 1165 | 1168 | ``` 1169 | 1170 | Editar `crm/urls.py` 1171 | 1172 | ```python 1173 | path('vuejs/', v.person_vuejs_list, name='person_vuejs_list'), 1174 | path('vuejs/json/', v.person_json, name='person_json'), 1175 | path('vuejs/create/', v.person_vuejs_create, name='person_vuejs_create'), 1176 | ``` 1177 | 1178 | 2. Editar `person_vuejs_list.html` 1179 | 1180 | ```html 1181 | 1182 | {% extends "base.html" %} 1183 | 1184 | {% block css %} 1185 | 1186 | 1198 | 1199 | {% endblock css %} 1200 | 1201 | {% block content %} 1202 | 1203 |
1204 |
1205 |

Pessoas com VueJS

1206 |
1207 | 1208 |
1209 |
1210 |
1211 | 1212 | 1213 |
1214 |
1215 | 1216 | 1217 |
1218 |
1219 | 1220 | 1221 |
1222 |
1223 | 1224 |
1225 |
1226 |
1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1240 | 1241 | 1242 | 1243 |
NomeE-mail
1238 | ${ person | fullName } 1239 | ${ person.email }
1244 |
1245 | 1246 | {% endblock content %} 1247 | 1248 | {% block vuejs %} 1249 | 1250 | 1282 | 1283 | {% endblock vuejs %} 1284 | ``` 1285 | 1286 | 3. Editar `crm/views.py` 1287 | 1288 | ```python 1289 | def person_vuejs_list(request): 1290 | # Renderiza a página 1291 | template_name = 'crm/person_vuejs_list.html' 1292 | return render(request, template_name) 1293 | 1294 | 1295 | def person_json(request): 1296 | # Retorna os dados 1297 | persons = Person.objects.all() 1298 | data = [person.to_dict() for person in persons] 1299 | return JsonResponse({'data': data}) 1300 | 1301 | 1302 | def person_vuejs_create(request): 1303 | # Salva os dados 1304 | form = PersonForm1(request.POST or None) 1305 | 1306 | if request.method == 'POST': 1307 | if form.is_valid(): 1308 | person = form.save() 1309 | data = person.to_dict() 1310 | return JsonResponse({'data': data}) 1311 | ``` 1312 | 1313 | 4. Fazer o Post via Axios 1314 | 1315 | ```js 1316 | methods: { 1317 | submitForm() { 1318 | // Caso queira enviar uma string de valores... 1319 | // const payload = JSON.stringify(this.form) 1320 | 1321 | // Vamos trabalhar com formulário 1322 | let bodyFormData = new FormData() 1323 | 1324 | bodyFormData.append('first_name', this.form.first_name) 1325 | bodyFormData.append('last_name', this.form.last_name) 1326 | bodyFormData.append('email', this.form.email) 1327 | 1328 | axios.post('/crm/vuejs/create/', bodyFormData) 1329 | .then(response => { 1330 | this.persons.push(response.data.data) 1331 | }) 1332 | this.form = {} 1333 | } 1334 | } 1335 | ``` 1336 | 1337 | ### Deletar itens 1338 | 1339 | Editar `urls.py` 1340 | 1341 | ```python 1342 | path('/vuejs/delete/', v.person_vuejs_delete, name='person_vuejs_delete'), 1343 | ``` 1344 | 1345 | Editar `views.py` 1346 | 1347 | ```python 1348 | def person_vuejs_delete(request, pk): 1349 | if request.method == 'DELETE': 1350 | person = Person.objects.get(pk=pk) 1351 | person.delete() 1352 | return JsonResponse({'status': 204}) 1353 | ``` 1354 | 1355 | Editar `person_vuejs_list.html` 1356 | 1357 | ```html 1358 | 1359 | 1360 | 1361 | ``` 1362 | 1363 | ```js 1364 | deletePerson(item) { 1365 | axios.delete(`/crm/${item.id}/vuejs/delete/`) 1366 | .then(() => { 1367 | const resIndex = this.persons.find(res => res.id === item.id); 1368 | this.persons.splice(resIndex, 1); 1369 | }) 1370 | } 1371 | ``` 1372 | 1373 | 1374 | ## 9 - Editando com VueJS 1375 | 1376 | ```python 1377 | # crm/forms.py 1378 | class PersonForm2(forms.ModelForm): 1379 | required_css_class = 'required' 1380 | 1381 | class Meta: 1382 | model = Person 1383 | fields = ('first_name', 'last_name', 'email') 1384 | 1385 | def __init__(self, *args, **kwargs): 1386 | super(PersonForm2, self).__init__(*args, **kwargs) 1387 | for field_name, field in self.fields.items(): 1388 | field.widget.attrs['class'] = 'form-control' 1389 | ``` 1390 | 1391 | 1392 | ```python 1393 | # crm/urls.py 1394 | ... 1395 | path('/vuejs/update/', v.person_vuejs_update, name='person_vuejs_update'), 1396 | ... 1397 | ``` 1398 | 1399 | 1400 | ```python 1401 | # crm/views.py 1402 | def person_vuejs_update(request, pk): 1403 | person = Person.objects.get(pk=pk) 1404 | form = PersonForm2(request.POST or None, instance=person) 1405 | 1406 | if request.method == 'POST': 1407 | if form.is_valid(): 1408 | person = form.save() 1409 | data = person.to_dict() 1410 | return JsonResponse({'data': data}) 1411 | ``` 1412 | 1413 | ```html 1414 | 1415 | ... 1416 | 1417 | ... 1418 | 1419 | 1451 | 1452 | 1487 | ``` 1488 | 1489 | 1490 | ![thor](https://raw.githubusercontent.com/rg3915/django-grupy-jundiai/master/img/thor.gif) 1491 | 1492 | Valeu! Até a próxima. 1493 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dj-database-url==0.5.0 2 | django-bootstrap4==3.0.1 3 | django-crispy-forms==1.11.2 4 | django-extensions==3.1.3 5 | django-localflavor==3.1 6 | django-widget-tweaks==1.4.8 7 | Django==3.2.* 8 | Faker==8.5.1 9 | isort==5.8.0 10 | python-decouple==3.4 11 | Pillow==8.2.0 12 | --------------------------------------------------------------------------------