├── core
├── __init__.py
├── views.py
├── apps.py
├── urls.py
├── static
│ └── css
│ │ └── style.css
└── templates
│ ├── index.html
│ ├── includes
│ ├── nav.html
│ └── pagination.html
│ └── base.html
├── backend
├── __init__.py
├── api.py
├── urls.py
├── asgi.py
├── wsgi.py
└── settings.py
├── bookstore
├── migrations
│ ├── __init__.py
│ └── 0001_initial.py
├── apps.py
├── templates
│ └── bookstore
│ │ ├── author_detail.html
│ │ ├── publisher_detail.html
│ │ ├── book_detail.html
│ │ ├── book_form.html
│ │ ├── author_form.html
│ │ ├── publisher_form.html
│ │ ├── author_confirm_delete.html
│ │ ├── book_confirm_delete.html
│ │ ├── publisher_confirm_delete.html
│ │ ├── author_list.html
│ │ ├── publisher_list.html
│ │ └── book_list.html
├── admin.py
├── api_drf
│ ├── serializers.py
│ └── viewsets.py
├── forms.py
├── models.py
├── views.py
├── urls.py
└── api.py
├── requirements.txt
├── manage.py
├── contrib
└── env_gen.py
├── README.md
├── .gitignore
└── passo-a-passo.md
/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bookstore/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 |
4 | def index(request):
5 | template_name = 'index.html'
6 | return render(request, template_name)
7 |
--------------------------------------------------------------------------------
/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 = 'core'
7 |
--------------------------------------------------------------------------------
/core/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from .views import index
4 |
5 | app_name = 'core'
6 |
7 | urlpatterns = [
8 | path('', index, name='index'),
9 | ]
10 |
--------------------------------------------------------------------------------
/backend/api.py:
--------------------------------------------------------------------------------
1 | from ninja import NinjaAPI
2 |
3 | from bookstore.api import router as bookstore_router
4 |
5 | api = NinjaAPI()
6 |
7 | api.add_router('/bookstore/', bookstore_router)
8 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/bookstore/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BookstoreConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'bookstore'
7 |
8 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==3.2.8
2 | djangorestframework==3.12.4
3 | dr-scaffold==2.1.1
4 | django-extensions==3.1.3
5 | django-seed==0.3.1
6 | python-decouple==3.5
7 | psycopg2-binary==2.9.1
8 | django-ninja==0.16.1
9 |
--------------------------------------------------------------------------------
/core/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
8 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/author_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Detalhes do Autor
6 |
7 |
8 | - Nome: {{ object.name }}
9 |
10 |
11 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/publisher_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Detalhes da Editora
6 |
7 |
8 | - Nome: {{ object.name }}
9 | - Pontos: {{ object.score }}
10 |
11 |
12 | {% endblock content %}
--------------------------------------------------------------------------------
/backend/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 | from .api import api
5 |
6 |
7 | urlpatterns = [
8 | path('', include('core.urls')),
9 | path('', include('bookstore.urls', namespace='bookstore')),
10 | path('api/v2/', api.urls),
11 | path('admin/', admin.site.urls),
12 | ]
13 |
--------------------------------------------------------------------------------
/backend/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for backend 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', 'backend.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/backend/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for backend 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', 'backend.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/book_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Detalhes do Livro
6 |
7 |
8 | - Nome: {{ object.name }}
9 | - ISBN: {{ object.isbn }}
10 | - Pontuação: {{ object.rating }}
11 | - Autores: {{ object.authors.all|join:", " }}
12 | - Editora: {{ object.publisher }}
13 | - Preço: {{ object.price }}
14 | - Estoque: {{ object.stock }}
15 |
16 |
17 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/book_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Adiciona Livro
6 |
7 |
18 |
19 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/author_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Adiciona Autor
6 |
7 |
18 |
19 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/publisher_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Adiciona Editora
6 |
7 |
18 |
19 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/author_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Deletar Autor
6 |
7 |
19 |
20 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/book_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Deletar Livro
6 |
7 |
19 |
20 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/publisher_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 | Deletar Editora
6 |
7 |
19 |
20 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from bookstore.models import Author, Book, Publisher
4 |
5 |
6 | @admin.register(Author)
7 | class AuthorAdmin(admin.ModelAdmin):
8 | list_display = ('__str__',)
9 | search_fields = ('name',)
10 |
11 |
12 | @admin.register(Publisher)
13 | class PublisherAdmin(admin.ModelAdmin):
14 | list_display = ('__str__', 'score')
15 | search_fields = ('name',)
16 |
17 |
18 | @admin.register(Book)
19 | class BookAdmin(admin.ModelAdmin):
20 | list_display = ('__str__', 'isbn', 'rating', 'price', 'stock')
21 | search_fields = ('name', 'isbn', 'authors__name', 'publisher__name')
22 |
--------------------------------------------------------------------------------
/bookstore/api_drf/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from bookstore.models import Author, Book, Publisher
4 |
5 |
6 | class AuthorSerializer(serializers.ModelSerializer):
7 |
8 | class Meta:
9 | model = Author
10 | fields = '__all__'
11 |
12 |
13 | class PublisherSerializer(serializers.ModelSerializer):
14 |
15 | class Meta:
16 | model = Publisher
17 | fields = '__all__'
18 |
19 |
20 | class BookSerializer(serializers.ModelSerializer):
21 | authors = AuthorSerializer(many=True)
22 | publisher = PublisherSerializer()
23 |
24 | class Meta:
25 | model = Book
26 | fields = '__all__'
27 |
--------------------------------------------------------------------------------
/bookstore/api_drf/viewsets.py:
--------------------------------------------------------------------------------
1 | from rest_framework import viewsets
2 |
3 | from bookstore.models import Author, Book, Publisher
4 | from bookstore.api_drf.serializers import (
5 | AuthorSerializer,
6 | BookSerializer,
7 | PublisherSerializer
8 | )
9 |
10 |
11 | class AuthorViewSet(viewsets.ModelViewSet):
12 | queryset = Author.objects.all()
13 | serializer_class = AuthorSerializer
14 |
15 |
16 | class PublisherViewSet(viewsets.ModelViewSet):
17 | queryset = Publisher.objects.all()
18 | serializer_class = PublisherSerializer
19 |
20 |
21 | class BookViewSet(viewsets.ModelViewSet):
22 | queryset = Book.objects.all()
23 | serializer_class = BookSerializer
24 |
--------------------------------------------------------------------------------
/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', 'backend.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 |
--------------------------------------------------------------------------------
/bookstore/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from .models import Author, Book, Publisher
4 |
5 |
6 | class _BaseModelForm(forms.ModelForm):
7 | required_css_class = 'required'
8 |
9 | def __init__(self, *args, **kwargs):
10 | super(_BaseModelForm, self).__init__(*args, **kwargs)
11 | for field_name, field in self.fields.items():
12 | field.widget.attrs['class'] = 'form-control'
13 |
14 |
15 | class AuthorForm(_BaseModelForm):
16 |
17 | class Meta:
18 | model = Author
19 | fields = '__all__'
20 |
21 |
22 | class PublisherForm(_BaseModelForm):
23 |
24 | class Meta:
25 | model = Publisher
26 | fields = '__all__'
27 |
28 |
29 | class BookForm(_BaseModelForm):
30 |
31 | class Meta:
32 | model = Book
33 | fields = '__all__'
34 |
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/author_list.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
12 |
13 |
14 |
15 |
16 | | Nome |
17 | Ações |
18 |
19 |
20 |
21 | {% for object in object_list %}
22 |
23 | |
24 | {{ object.name }}
25 | |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | |
34 |
35 | {% endfor %}
36 |
37 |
38 |
39 | {% include "includes/pagination.html" %}
40 |
41 | {% endblock content %}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/core/templates/includes/nav.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/publisher_list.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
12 |
13 |
14 |
15 |
16 | | Nome |
17 | Pontos |
18 | Ações |
19 |
20 |
21 |
22 | {% for object in object_list %}
23 |
24 | |
25 | {{ object.name }}
26 | |
27 | {{ object.score }} |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | |
36 |
37 | {% endfor %}
38 |
39 |
40 |
41 | {% include "includes/pagination.html" %}
42 |
43 | {% endblock content %}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bookstore/templates/bookstore/book_list.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
12 |
13 |
14 |
15 |
16 | | Nome |
17 | ISBN |
18 | Pontuação |
19 | Autores |
20 | Editora |
21 | Preço |
22 | Estoque |
23 | Ações |
24 |
25 |
26 |
27 | {% for object in object_list %}
28 |
29 | |
30 | {{ object.name }}
31 | |
32 | {{ object.isbn }} |
33 | {{ object.rating }} |
34 | {{ object.authors.all|join:", " }} |
35 | {{ object.publisher }} |
36 | {{ object.price }} |
37 | {{ object.stock }} |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | |
46 |
47 | {% endfor %}
48 |
49 |
50 |
51 | {% include "includes/pagination.html" %}
52 |
53 | {% endblock content %}
--------------------------------------------------------------------------------
/bookstore/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.urls import reverse_lazy
3 |
4 |
5 | class Author(models.Model):
6 | name = models.CharField(max_length=50, unique=True)
7 |
8 | def __str__(self):
9 | return self.name
10 |
11 | class Meta:
12 | ordering = ('name',)
13 | verbose_name = 'Autor'
14 | verbose_name_plural = 'Autores'
15 |
16 | def get_absolute_url(self):
17 | return reverse_lazy('bookstore:author_detail', kwargs={'pk': self.pk})
18 |
19 |
20 | class Publisher(models.Model):
21 | name = models.CharField(max_length=50, unique=True)
22 | score = models.PositiveIntegerField(default=0)
23 |
24 | def __str__(self):
25 | return self.name
26 |
27 | class Meta:
28 | ordering = ('name',)
29 | verbose_name = 'Editora'
30 | verbose_name_plural = 'Editoras'
31 |
32 | def get_absolute_url(self):
33 | return reverse_lazy('bookstore:publisher_detail', kwargs={'pk': self.pk})
34 |
35 |
36 | class Book(models.Model):
37 | name = models.CharField(max_length=100)
38 | isbn = models.CharField(max_length=13)
39 | rating = models.DecimalField(max_digits=5, decimal_places=2, null=True, default=0.0)
40 | authors = models.ManyToManyField(Author)
41 | publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, null=True)
42 | price = models.DecimalField(max_digits=5, decimal_places=2, null=True, default=0.0)
43 | stock = models.IntegerField(default=0)
44 | created = models.DateTimeField(auto_now_add=True)
45 |
46 | def __str__(self):
47 | return self.name
48 |
49 | class Meta:
50 | ordering = ('name',)
51 | verbose_name = 'Livro'
52 | verbose_name_plural = 'Livros'
53 |
54 | def get_absolute_url(self):
55 | return reverse_lazy('bookstore:book_detail', kwargs={'pk': self.pk})
56 |
--------------------------------------------------------------------------------
/bookstore/views.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse_lazy
2 | from django.views.generic import (
3 | CreateView,
4 | DeleteView,
5 | DetailView,
6 | ListView,
7 | UpdateView
8 | )
9 |
10 | from .forms import AuthorForm, BookForm, PublisherForm
11 | from .models import Author, Book, Publisher
12 |
13 |
14 | class AuthorListView(ListView):
15 | model = Author
16 | paginate_by = 20
17 |
18 |
19 | class AuthorDetailView(DetailView):
20 | model = Author
21 |
22 |
23 | class AuthorCreateView(CreateView):
24 | model = Author
25 | form_class = AuthorForm
26 |
27 |
28 | class AuthorUpdateView(UpdateView):
29 | model = Author
30 | form_class = AuthorForm
31 |
32 |
33 | class AuthorDeleteView(DeleteView):
34 | model = Author
35 | success_url = reverse_lazy('bookstore:author_list')
36 |
37 |
38 | class PublisherListView(ListView):
39 | model = Publisher
40 | paginate_by = 20
41 |
42 |
43 | class PublisherDetailView(DetailView):
44 | model = Publisher
45 |
46 |
47 | class PublisherCreateView(CreateView):
48 | model = Publisher
49 | form_class = PublisherForm
50 |
51 |
52 | class PublisherUpdateView(UpdateView):
53 | model = Publisher
54 | form_class = PublisherForm
55 |
56 |
57 | class PublisherDeleteView(DeleteView):
58 | model = Publisher
59 | success_url = reverse_lazy('bookstore:publisher_list')
60 |
61 |
62 | class BookListView(ListView):
63 | model = Book
64 | paginate_by = 20
65 |
66 |
67 | class BookDetailView(DetailView):
68 | model = Book
69 |
70 |
71 | class BookCreateView(CreateView):
72 | model = Book
73 | form_class = BookForm
74 |
75 |
76 | class BookUpdateView(UpdateView):
77 | model = Book
78 | form_class = BookForm
79 |
80 |
81 | class BookDeleteView(DeleteView):
82 | model = Book
83 | success_url = reverse_lazy('bookstore:book_list')
84 |
--------------------------------------------------------------------------------
/bookstore/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import include, path
2 | from rest_framework import routers
3 |
4 | from bookstore import views as v
5 | from bookstore.api_drf.viewsets import (
6 | AuthorViewSet,
7 | BookViewSet,
8 | PublisherViewSet
9 | )
10 |
11 | app_name = 'bookstore'
12 |
13 | router = routers.DefaultRouter()
14 |
15 | router.register(r'authors', AuthorViewSet)
16 | router.register(r'publishers', PublisherViewSet)
17 | router.register(r'books', BookViewSet)
18 |
19 | authors_urlpatterns = [
20 | path('', v.AuthorListView.as_view(), name='author_list'),
21 | path('/', v.AuthorDetailView.as_view(), name='author_detail'),
22 | path('create/', v.AuthorCreateView.as_view(), name='author_create'),
23 | path('/update/', v.AuthorUpdateView.as_view(), name='author_update'),
24 | path('/delete/', v.AuthorDeleteView.as_view(), name='author_delete'),
25 | ]
26 |
27 | publishers_urlpatterns = [
28 | path('', v.PublisherListView.as_view(), name='publisher_list'),
29 | path('/', v.PublisherDetailView.as_view(), name='publisher_detail'),
30 | path('create/', v.PublisherCreateView.as_view(), name='publisher_create'),
31 | path('/update/', v.PublisherUpdateView.as_view(), name='publisher_update'),
32 | path('/delete/', v.PublisherDeleteView.as_view(), name='publisher_delete'),
33 | ]
34 |
35 | books_urlpatterns = [
36 | path('', v.BookListView.as_view(), name='book_list'),
37 | path('/', v.BookDetailView.as_view(), name='book_detail'),
38 | path('create/', v.BookCreateView.as_view(), name='book_create'),
39 | path('/update/', v.BookUpdateView.as_view(), name='book_update'),
40 | path('/delete/', v.BookDeleteView.as_view(), name='book_delete'),
41 | ]
42 |
43 | urlpatterns = [
44 | path('api/v1/bookstore/', include(router.urls)),
45 | path('author/', include(authors_urlpatterns)),
46 | path('publisher/', include(publishers_urlpatterns)),
47 | path('book/', include(books_urlpatterns)),
48 | ]
49 |
--------------------------------------------------------------------------------
/core/templates/includes/pagination.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bookstore
2 |
3 | Exemplo de biblioteca feito com Django.
4 |
5 |
6 | ## Este projeto foi feito com:
7 |
8 | * [Python 3.9.7](https://www.python.org/)
9 | * [Django 3.2.8](https://www.djangoproject.com/)
10 | * [Django Rest Framework 3.12.4](https://www.django-rest-framework.org/)
11 | * [Bootstrap 4.0](https://getbootstrap.com/)
12 | * [VueJS 2.6.11](https://vuejs.org/)
13 | * [jQuery 3.4.1](https://jquery.com/)
14 |
15 | ## Como rodar o projeto?
16 |
17 | * Clone esse repositório.
18 | * Crie um virtualenv com Python 3.
19 | * Ative o virtualenv.
20 | * Instale as dependências.
21 | * Rode as migrações.
22 |
23 | ```
24 | git clone https://github.com/rg3915/bookstore.git
25 | cd bookstore
26 | python -m venv .venv
27 | source .venv/bin/activate
28 | pip install -r requirements.txt
29 | python contrib/env_gen.py
30 | python manage.py migrate
31 | python manage.py createsuperuser --username="admin" --email=""
32 | ```
33 |
34 | # Passo a passo
35 |
36 | https://github.com/rg3915/bookstore/blob/main/passo-a-passo.md
37 |
38 | ---
39 |
40 | ## Vídeos no meu canal
41 |
42 | * [A Essência do Django](https://youtu.be/mlaCLGItR7Q)
43 | * [Introdução a Arquitetura do Django - Pyjamas 2019](https://youtu.be/XjXpwZhOKOs)
44 | * [VueJS + Django Ninja](https://youtu.be/cZ7n3HN9MiU)
45 | * [Python-triangulo: Django: FBV vs CBV](https://www.youtube.com/watch?v=2qZcPb8ZWQA)
46 | * [Regis do Python](https://www.youtube.com/regis-do-python)
47 |
48 |
49 | ## Links
50 |
51 | * https://www.python.org/
52 | * https://www.djangoproject.com/
53 | * http://pythonclub.com.br/
54 | * https://getbootstrap.com/
55 | * https://docs.djangoproject.com/en/3.2/ref/models/fields/
56 | * https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css
57 | * https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css
58 | * https://code.jquery.com/jquery-3.4.1.min.js
59 | * https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js
60 | * https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js
61 | * https://getbootstrap.com/docs/4.0/examples/starter-template/
62 | * https://github.com/JTruax/bootstrap-starter-template/blob/master/template/start.html
63 | * https://ccbv.co.uk/
64 |
65 |
66 | ## Livros
67 |
68 | * [Pense Python 2e](https://penseallen.github.io/PensePython2e/)
69 | * Aprenda Django 3 com Exemplos
70 | * Two Scoops of Django
71 |
72 |
--------------------------------------------------------------------------------
/bookstore/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.8 on 2021-10-25 10:12
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Author',
17 | fields=[
18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('name', models.CharField(max_length=50, unique=True)),
20 | ],
21 | options={
22 | 'verbose_name': 'Autor',
23 | 'verbose_name_plural': 'Autores',
24 | 'ordering': ('name',),
25 | },
26 | ),
27 | migrations.CreateModel(
28 | name='Publisher',
29 | fields=[
30 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31 | ('name', models.CharField(max_length=50, unique=True)),
32 | ('score', models.PositiveIntegerField(default=0)),
33 | ],
34 | options={
35 | 'verbose_name': 'Editora',
36 | 'verbose_name_plural': 'Editoras',
37 | 'ordering': ('name',),
38 | },
39 | ),
40 | migrations.CreateModel(
41 | name='Book',
42 | fields=[
43 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
44 | ('name', models.CharField(max_length=100)),
45 | ('isbn', models.CharField(max_length=13)),
46 | ('rating', models.DecimalField(decimal_places=2, default=0.0, max_digits=5, null=True)),
47 | ('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=5, null=True)),
48 | ('stock', models.IntegerField(default=0)),
49 | ('created', models.DateTimeField(auto_now_add=True)),
50 | ('authors', models.ManyToManyField(to='bookstore.Author')),
51 | ('publisher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookstore.publisher')),
52 | ],
53 | options={
54 | 'verbose_name': 'Livro',
55 | 'verbose_name_plural': 'Livros',
56 | 'ordering': ('name',),
57 | },
58 | ),
59 | ]
60 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/backend/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for backend project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.2.8.
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 | from pathlib import Path
13 |
14 | from decouple import Csv, config
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = config('SECRET_KEY')
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = config('DEBUG', default=False, cast=bool)
27 |
28 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
29 |
30 | INSTALLED_APPS = [
31 | 'django.contrib.admin',
32 | 'django.contrib.auth',
33 | 'django.contrib.contenttypes',
34 | 'django.contrib.sessions',
35 | 'django.contrib.messages',
36 | 'django.contrib.staticfiles',
37 | # 3rd apps
38 | 'dr_scaffold',
39 | 'rest_framework',
40 | 'django_extensions',
41 | 'django_seed',
42 | # my apps
43 | 'core',
44 | 'bookstore',
45 | ]
46 |
47 | MIDDLEWARE = [
48 | 'django.middleware.security.SecurityMiddleware',
49 | 'django.contrib.sessions.middleware.SessionMiddleware',
50 | 'django.middleware.common.CommonMiddleware',
51 | 'django.middleware.csrf.CsrfViewMiddleware',
52 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
53 | 'django.contrib.messages.middleware.MessageMiddleware',
54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
55 | ]
56 |
57 | ROOT_URLCONF = 'backend.urls'
58 |
59 | TEMPLATES = [
60 | {
61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
62 | 'DIRS': [],
63 | 'APP_DIRS': True,
64 | 'OPTIONS': {
65 | 'context_processors': [
66 | 'django.template.context_processors.debug',
67 | 'django.template.context_processors.request',
68 | 'django.contrib.auth.context_processors.auth',
69 | 'django.contrib.messages.context_processors.messages',
70 | ],
71 | },
72 | },
73 | ]
74 |
75 | WSGI_APPLICATION = 'backend.wsgi.application'
76 |
77 |
78 | # Database
79 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
80 |
81 | DATABASES = {
82 | 'default': {
83 | 'ENGINE': 'django.db.backends.sqlite3',
84 | 'NAME': BASE_DIR / 'db.sqlite3',
85 | }
86 | }
87 |
88 |
89 | # Password validation
90 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
91 |
92 | AUTH_PASSWORD_VALIDATORS = [
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
95 | },
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
101 | },
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
104 | },
105 | ]
106 |
107 |
108 | # Internationalization
109 | # https://docs.djangoproject.com/en/3.2/topics/i18n/
110 |
111 | LANGUAGE_CODE = 'pt-br'
112 |
113 | TIME_ZONE = 'America/Sao_Paulo'
114 |
115 | USE_I18N = True
116 |
117 | USE_L10N = True
118 |
119 | USE_TZ = True
120 |
121 |
122 | # Static files (CSS, JavaScript, Images)
123 | # https://docs.djangoproject.com/en/3.2/howto/static-files/
124 |
125 | STATIC_URL = '/static/'
126 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles')
127 |
128 | # Default primary key field type
129 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
130 |
131 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
132 |
--------------------------------------------------------------------------------
/bookstore/api.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from django.shortcuts import get_object_or_404
4 | from ninja import Router, Schema
5 | from ninja.orm import create_schema
6 |
7 | from .models import Author, Book, Publisher
8 |
9 | router = Router()
10 |
11 | AuthorSchema = create_schema(Author)
12 | PublisherSchema = create_schema(Publisher)
13 | BookSchema = create_schema(Book, depth=1)
14 |
15 |
16 | class AuthorSchemaIn(Schema):
17 | name: str
18 |
19 |
20 | class PublisherSchemaIn(Schema):
21 | name: str
22 | score: int
23 |
24 |
25 | class BookSchemaIn(Schema):
26 | name: str
27 | isbn: str
28 | rating: float
29 | publisher_id: int
30 | price: float
31 | stock: int
32 | authors: List[int]
33 |
34 |
35 | @router.get("/books", response=List[BookSchema])
36 | def list_books(request):
37 | qs = Book.objects.all()
38 | return qs
39 |
40 |
41 | @router.get("/books/{id}", response=BookSchema)
42 | def get_book(request, id: int):
43 | book = get_object_or_404(Book, id=id)
44 | return book
45 |
46 |
47 | @router.post("/books", response={201: BookSchema})
48 | def create_book(request, payload: BookSchemaIn):
49 | # Get params
50 | authors = payload.dict().pop('authors')
51 | publisher_id = payload.dict().pop('publisher_id')
52 |
53 | # Instance models
54 | # Get publisher
55 | publisher = get_object_or_404(Publisher, id=publisher_id)
56 |
57 | # Mount dict data
58 | data = {}
59 | for k, v in payload.dict().items():
60 | data[k] = v
61 |
62 | data['publisher'] = publisher
63 | # Necessário porque é uma lista, mas não pode ser salvo diretamente.
64 | data.pop('authors', None)
65 |
66 | # Save data
67 | book = Book.objects.create(**data)
68 |
69 | # Add authors
70 | for author in authors:
71 | # Get authors
72 | author_obj = get_object_or_404(Author, id=author)
73 | book.authors.add(author_obj)
74 |
75 | return 201, book
76 |
77 |
78 | @router.put("/books/{id}", response=BookSchema)
79 | def update_book(request, id: int, payload: BookSchemaIn):
80 | book = get_object_or_404(Book, id=id)
81 | for attr, value in payload.dict().items():
82 | if attr != 'authors':
83 | setattr(book, attr, value)
84 | book.save()
85 |
86 | authors = payload.dict().pop('authors')
87 |
88 | # Remove all authors
89 | book.authors.clear()
90 |
91 | # Add authors
92 | for author in authors:
93 | # Get authors
94 | author_obj = get_object_or_404(Author, id=author)
95 | book.authors.add(author_obj)
96 |
97 | return book
98 |
99 |
100 | @router.delete("/books/{id}", response={204: None})
101 | def delete_book(request, id: int):
102 | book = get_object_or_404(Book, id=id)
103 | book.delete()
104 | return 204, None
105 |
106 |
107 | @router.get("/authors", response=List[AuthorSchema])
108 | def list_authors(request):
109 | qs = Author.objects.all()
110 | return qs
111 |
112 |
113 | @router.get("/authors/{id}", response=AuthorSchema)
114 | def get_author(request, id: int):
115 | author = get_object_or_404(Author, id=id)
116 | return author
117 |
118 |
119 | @router.post("/authors", response={201: AuthorSchema})
120 | def create_author(request, payload: AuthorSchemaIn):
121 | author = Author.objects.create(**payload.dict())
122 | return 201, author
123 |
124 |
125 | @router.put("/authors/{id}", response=AuthorSchema)
126 | def update_author(request, id: int, payload: AuthorSchemaIn):
127 | author = get_object_or_404(Author, id=id)
128 | for attr, value in payload.dict().items():
129 | setattr(author, attr, value)
130 | author.save()
131 | return author
132 |
133 |
134 | @router.delete("/authors/{id}", response={204: None})
135 | def delete_author(request, id: int):
136 | author = get_object_or_404(Author, id=id)
137 | author.delete()
138 | return 204, None
139 |
140 |
141 | @router.get("/publishers", response=List[PublisherSchema])
142 | def list_publishers(request):
143 | qs = Publisher.objects.all()
144 | return qs
145 |
146 |
147 | @router.get("/publishers/{id}", response=PublisherSchema)
148 | def get_publisher(request, id: int):
149 | publisher = get_object_or_404(Publisher, id=id)
150 | return publisher
151 |
152 |
153 | @router.post("/publishers", response={201: PublisherSchema})
154 | def create_publisher(request, payload: PublisherSchemaIn):
155 | publisher = Publisher.objects.create(**payload.dict())
156 | return 201, publisher
157 |
158 |
159 | @router.put("/publishers/{id}", response=PublisherSchema)
160 | def update_publisher(request, id: int, payload: PublisherSchemaIn):
161 | publisher = get_object_or_404(Publisher, id=id)
162 | for attr, value in payload.dict().items():
163 | setattr(publisher, attr, value)
164 | publisher.save()
165 | return publisher
166 |
167 |
168 | @router.delete("/publishers/{id}", response={204: None})
169 | def delete_publisher(request, id: int):
170 | publisher = get_object_or_404(Publisher, id=id)
171 | publisher.delete()
172 | return 204, None
173 |
--------------------------------------------------------------------------------
/passo-a-passo.md:
--------------------------------------------------------------------------------
1 | # Passo a passo
2 |
3 | ## Criando virtualenv
4 |
5 | ```
6 | python -m venv .venv
7 | source .venv/bin/activate
8 | ```
9 |
10 |
11 | ## Instalando os pacotes
12 |
13 | ```
14 | pip install dr_scaffold djangorestframework
15 |
16 | pip freeze | grep 'Django\|djangorestframework\|dr-scaffold' >> requirements.txt
17 | ```
18 |
19 |
20 | ```
21 | pip install django-extensions python-decouple django-seed
22 |
23 | pip freeze | grep 'django-extensions\|python-decouple\|django-seed\|faker' >> requirements.txt
24 | ```
25 |
26 | ## Criando o projeto
27 |
28 | `django-admin startproject backend .`
29 |
30 | > Editar `settings.py` e incluir `dr_scaffold` em `INSTALLED_APPS`.
31 |
32 | ```python
33 | from pathlib import Path
34 |
35 | from decouple import Csv, config
36 |
37 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
38 | BASE_DIR = Path(__file__).resolve().parent.parent
39 |
40 | # Quick-start development settings - unsuitable for production
41 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
42 |
43 | # SECURITY WARNING: keep the secret key used in production secret!
44 | SECRET_KEY = config('SECRET_KEY')
45 |
46 | # SECURITY WARNING: don't run with debug turned on in production!
47 | DEBUG = config('DEBUG', default=False, cast=bool)
48 |
49 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
50 |
51 | INSTALLED_APPS = [
52 | 'django.contrib.admin',
53 | 'django.contrib.auth',
54 | 'django.contrib.contenttypes',
55 | 'django.contrib.sessions',
56 | 'django.contrib.messages',
57 | 'django.contrib.staticfiles',
58 | # 3rd apps
59 | 'dr_scaffold',
60 | 'rest_framework',
61 | 'django_extensions',
62 | 'django_seed',
63 | # my apps
64 | ]
65 |
66 |
67 | LANGUAGE_CODE = 'pt-br'
68 |
69 | TIME_ZONE = 'America/Sao_Paulo'
70 |
71 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles')
72 | ```
73 |
74 | #### contrib
75 |
76 | https://github.com/rg3915/bookstore/blob/main/contrib/env_gen.py
77 |
78 | ```
79 | mkdir contrib
80 | touch contrib/env_gen.py
81 | python contrib/env_gen.py
82 | ```
83 |
84 |
85 | ```
86 | python manage.py dr_scaffold bookstore Author name:charfield
87 |
88 | python manage.py dr_scaffold bookstore Publisher name:charfield score:positiveintegerfield
89 |
90 | python manage.py dr_scaffold bookstore Book \
91 | name:charfield \
92 | isbn:charfield \
93 | rating:decimalfield \
94 | authors:manytomany:Author \
95 | publisher:foreignkey:Publisher \
96 | price:decimalfield \
97 | stock:integerfield
98 | ```
99 |
100 |
101 | > Editar `models.py`
102 |
103 | ```python
104 | from django.db import models
105 |
106 |
107 | class Author(models.Model):
108 | name = models.CharField(max_length=50, unique=True)
109 |
110 | def __str__(self):
111 | return self.name
112 |
113 | class Meta:
114 | ordering = ('name',)
115 | verbose_name = 'Autor'
116 | verbose_name_plural = 'Autores'
117 |
118 |
119 | class Publisher(models.Model):
120 | name = models.CharField(max_length=50, unique=True)
121 | score = models.PositiveIntegerField(default=0)
122 |
123 | def __str__(self):
124 | return self.name
125 |
126 | class Meta:
127 | ordering = ('name',)
128 | verbose_name = 'Editora'
129 | verbose_name_plural = 'Editoras'
130 |
131 |
132 | class Book(models.Model):
133 | name = models.CharField(max_length=100)
134 | isbn = models.CharField(max_length=13)
135 | rating = models.DecimalField(max_digits=5, decimal_places=2, null=True, default=0.0)
136 | authors = models.ManyToManyField(Author)
137 | publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, null=True)
138 | price = models.DecimalField(max_digits=5, decimal_places=2, null=True, default=0.0)
139 | stock = models.IntegerField(default=0)
140 | created = models.DateTimeField(auto_now_add=True)
141 |
142 | def __str__(self):
143 | return self.name
144 |
145 | class Meta:
146 | ordering = ('name',)
147 | verbose_name = 'Livro'
148 | verbose_name_plural = 'Livros'
149 |
150 | ```
151 |
152 | #### Rode as migrações
153 |
154 | > Adicione `bookstore` em `INSTALLED_APPS`.
155 |
156 | ```
157 | python manage.py makemigrations
158 | python manage.py migrate
159 | python manage.py createsuperuser
160 | ```
161 |
162 |
163 | ```
164 | python manage.py seed bookstore --number=160
165 | ```
166 |
167 |
168 | ```
169 | pip install psycopg2-binary
170 |
171 | pip freeze | grep psycopg2-binary >> requirements.txt
172 | ```
173 |
174 |
175 | ```
176 | python manage.py runserver
177 | ```
178 |
179 |
180 | > Editar `admin.py`
181 |
182 | ```python
183 | from django.contrib import admin
184 |
185 | from bookstore.models import Author, Book, Publisher
186 |
187 |
188 | @admin.register(Author)
189 | class AuthorAdmin(admin.ModelAdmin):
190 | list_display = ('__str__',)
191 | search_fields = ('name',)
192 |
193 |
194 | @admin.register(Publisher)
195 | class PublisherAdmin(admin.ModelAdmin):
196 | list_display = ('__str__', 'score')
197 | search_fields = ('name',)
198 |
199 |
200 | @admin.register(Book)
201 | class BookAdmin(admin.ModelAdmin):
202 | list_display = ('__str__', 'isbn', 'rating', 'price', 'stock')
203 | search_fields = ('name', 'isbn', 'authors__name', 'publisher__name')
204 |
205 | ```
206 |
207 | > Abrir `shell_plus`
208 |
209 | `python manage.py shell_plus`
210 |
211 |
212 | ```python
213 | import string
214 | from random import choice, randint, random
215 |
216 | from faker import Faker
217 |
218 | faker = Faker('pt-br')
219 |
220 | def gen_digits(max_length):
221 | return str(''.join(choice(string.digits) for i in range(max_length)))
222 |
223 | def gen_rating():
224 | return random() + choice((3, 4))
225 |
226 | def gen_price():
227 | return random() + randint(10, 100)
228 |
229 | def gen_name():
230 | return faker.name()
231 |
232 | books = Book.objects.all()
233 | for book in books:
234 | book.isbn = '978' + gen_digits(10)
235 | book.rating = gen_rating()
236 | book.price = gen_price()
237 |
238 | Book.objects.bulk_update(books, ['isbn', 'rating', 'price'])
239 |
240 | authors = Author.objects.all()
241 | for _ in authors:
242 | Author.objects.update_or_create(name=gen_name())
243 | ```
244 |
245 |
246 | > Crie uma pasta `api_drf` e mova `serializers.py` e `viewsets.py` para lá.
247 |
248 |
249 | ```
250 | cd bookstore
251 | mkdir api_drf
252 | mv serializers.py api_drf
253 | mv views.py api_drf/viewsets.py
254 | cd ..
255 | ```
256 |
257 |
258 | > Editar `backend/urls.py`
259 |
260 | ```python
261 | from django.contrib import admin
262 | from django.urls import include, path
263 |
264 |
265 | urlpatterns = [
266 | path('', include('bookstore.urls', namespace='bookstore')),
267 | path('admin/', admin.site.urls),
268 | ]
269 | ```
270 |
271 | > Editar `bookstore/urls.py`
272 |
273 | ```python
274 | from bookstore.api_drf.viewsets import AuthorViewSet, BookViewSet, PublisherViewSet
275 |
276 | app_name = 'bookstore'
277 |
278 | urlpatterns = [
279 | path('api/v1/bookstore/', include(router.urls)),
280 | ]
281 | ```
282 |
283 | > Editar `bookstore/api/viewsets.py`
284 |
285 | ```python
286 | from bookstore.api_drf.serializers import ...
287 |
288 | ```
289 |
290 | > Editar `bookstore/serializers.py`
291 |
292 | ```python
293 | from rest_framework import serializers
294 |
295 | from bookstore.models import Author, Book, Publisher
296 |
297 |
298 | class AuthorSerializer(serializers.ModelSerializer):
299 |
300 | class Meta:
301 | model = Author
302 | fields = '__all__'
303 |
304 |
305 | class PublisherSerializer(serializers.ModelSerializer):
306 |
307 | class Meta:
308 | model = Publisher
309 | fields = '__all__'
310 |
311 |
312 | class BookSerializer(serializers.ModelSerializer):
313 | authors = AuthorSerializer(many=True)
314 | publisher = PublisherSerializer()
315 |
316 | class Meta:
317 | model = Book
318 | fields = '__all__'
319 |
320 | ```
321 |
322 | ## Django Ninja
323 |
324 | ```
325 | pip install django-ninja
326 |
327 | pip freeze | grep django-ninja >> requirements.txt
328 | ```
329 |
330 | ```
331 | touch backend/api.py
332 | touch bookstore/api.py
333 | ```
334 |
335 | > Editar `backend/api.py`
336 |
337 | ```python
338 | from ninja import NinjaAPI
339 |
340 | from bookstore.api import router as bookstore_router
341 |
342 | api = NinjaAPI()
343 |
344 | api.add_router('/bookstore/', bookstore_router)
345 |
346 | ```
347 |
348 | > Editar `bookstore/api.py` primeiro `books`
349 |
350 | ```python
351 | from typing import List
352 |
353 | from django.shortcuts import get_object_or_404
354 | from ninja import Router, Schema
355 | from ninja.orm import create_schema
356 |
357 | from .models import Author, Book, Publisher
358 |
359 | router = Router()
360 |
361 | BookSchema = create_schema(Book, depth=1)
362 |
363 |
364 | class BookSchemaIn(Schema):
365 | name: str
366 | isbn: str
367 | rating: float
368 | publisher_id: int
369 | price: float
370 | stock: int
371 | authors: List[int]
372 |
373 |
374 | @router.get("/books", response=List[BookSchema])
375 | def list_books(request):
376 | qs = Book.objects.all()
377 | return qs
378 |
379 |
380 | @router.get("/books/{id}", response=BookSchema)
381 | def get_book(request, id: int):
382 | book = get_object_or_404(Book, id=id)
383 | return book
384 |
385 |
386 | @router.post("/books", response={201: BookSchema})
387 | def create_book(request, payload: BookSchemaIn):
388 | # Get params
389 | authors = payload.dict().pop('authors')
390 | publisher_id = payload.dict().pop('publisher_id')
391 |
392 | # Instance models
393 | # Get publisher
394 | publisher = get_object_or_404(Publisher, id=publisher_id)
395 |
396 | # Mount dict data
397 | data = {}
398 | for k, v in payload.dict().items():
399 | data[k] = v
400 |
401 | data['publisher'] = publisher
402 | # Necessário porque é uma lista, mas não pode ser salvo diretamente.
403 | data.pop('authors', None)
404 |
405 | # Save data
406 | book = Book.objects.create(**data)
407 |
408 | # Add authors
409 | for author in authors:
410 | # Get authors
411 | author_obj = get_object_or_404(Author, id=author)
412 | book.authors.add(author_obj)
413 |
414 | return 201, book
415 |
416 |
417 | @router.put("/books/{id}", response=BookSchema)
418 | def update_book(request, id: int, payload: BookSchemaIn):
419 | book = get_object_or_404(Book, id=id)
420 | for attr, value in payload.dict().items():
421 | if attr != 'authors':
422 | setattr(book, attr, value)
423 | book.save()
424 |
425 | authors = payload.dict().pop('authors')
426 |
427 | # Remove all authors
428 | book.authors.clear()
429 |
430 | # Add authors
431 | for author in authors:
432 | # Get authors
433 | author_obj = get_object_or_404(Author, id=author)
434 | book.authors.add(author_obj)
435 |
436 | return book
437 |
438 |
439 | @router.delete("/books/{id}", response={204: None})
440 | def delete_book(request, id: int):
441 | book = get_object_or_404(Book, id=id)
442 | book.delete()
443 | return 204, None
444 |
445 | ```
446 |
447 | > depois `authors, publishers`
448 |
449 | ```python
450 | AuthorSchema = create_schema(Author)
451 | PublisherSchema = create_schema(Publisher)
452 |
453 |
454 | class AuthorSchemaIn(Schema):
455 | name: str
456 |
457 |
458 | class PublisherSchemaIn(Schema):
459 | name: str
460 | score: int
461 |
462 | @router.get("/authors", response=List[AuthorSchema])
463 | def list_authors(request):
464 | qs = Author.objects.all()
465 | return qs
466 |
467 |
468 | @router.get("/authors/{id}", response=AuthorSchema)
469 | def get_author(request, id: int):
470 | author = get_object_or_404(Author, id=id)
471 | return author
472 |
473 |
474 | @router.post("/authors", response={201: AuthorSchema})
475 | def create_author(request, payload: AuthorSchemaIn):
476 | author = Author.objects.create(**payload.dict())
477 | return 201, author
478 |
479 |
480 | @router.put("/authors/{id}", response=AuthorSchema)
481 | def update_author(request, id: int, payload: AuthorSchemaIn):
482 | author = get_object_or_404(Author, id=id)
483 | for attr, value in payload.dict().items():
484 | setattr(author, attr, value)
485 | author.save()
486 | return author
487 |
488 |
489 | @router.delete("/authors/{id}", response={204: None})
490 | def delete_author(request, id: int):
491 | author = get_object_or_404(Author, id=id)
492 | author.delete()
493 | return 204, None
494 |
495 |
496 | @router.get("/publishers", response=List[PublisherSchema])
497 | def list_publishers(request):
498 | qs = Publisher.objects.all()
499 | return qs
500 |
501 |
502 | @router.get("/publishers/{id}", response=PublisherSchema)
503 | def get_publisher(request, id: int):
504 | publisher = get_object_or_404(Publisher, id=id)
505 | return publisher
506 |
507 |
508 | @router.post("/publishers", response={201: PublisherSchema})
509 | def create_publisher(request, payload: PublisherSchemaIn):
510 | publisher = Publisher.objects.create(**payload.dict())
511 | return 201, publisher
512 |
513 |
514 | @router.put("/publishers/{id}", response=PublisherSchema)
515 | def update_publisher(request, id: int, payload: PublisherSchemaIn):
516 | publisher = get_object_or_404(Publisher, id=id)
517 | for attr, value in payload.dict().items():
518 | setattr(publisher, attr, value)
519 | publisher.save()
520 | return publisher
521 |
522 |
523 | @router.delete("/publishers/{id}", response={204: None})
524 | def delete_publisher(request, id: int):
525 | publisher = get_object_or_404(Publisher, id=id)
526 | publisher.delete()
527 | return 204, None
528 |
529 | ```
530 |
531 | > Editar `backend/urls.py`
532 |
533 | ```python
534 | from .api import api
535 |
536 | ...
537 | path('api/v2/', api.urls),
538 | ...
539 |
540 | ```
541 |
542 |
543 | ## Templates
544 |
545 | ```
546 | python manage.py startapp core
547 | rm -f core/{admin,models,tests}.py
548 | rm -rf core/migrations
549 | touch core/urls.py
550 | ```
551 |
552 |
553 | > Editar `settings.py`
554 |
555 | ```python
556 | INSTALLED_APPS = [
557 | ...
558 | 'core',
559 | ...
560 | ```
561 | > Editar `backend/urls.py`
562 |
563 | ```python
564 | path('', include('core.urls')),
565 | ```
566 |
567 | > Editar `core/urls.py`
568 |
569 | ```python
570 | from django.urls import path
571 |
572 | from .views import index
573 |
574 | app_name = 'core'
575 |
576 | urlpatterns = [
577 | path('', index, name='index'),
578 | ]
579 | ```
580 |
581 | > Editar `core/views.py`
582 |
583 | ```python
584 | from django.shortcuts import render
585 |
586 |
587 | def index(request):
588 | template_name = 'index.html'
589 | return render(request, template_name)
590 |
591 | ```
592 |
593 | ```
594 | mkdir -p core/static/css
595 | touch core/static/css/style.css
596 |
597 | mkdir -p core/templates/includes
598 | touch core/templates/{base,index}.html
599 | touch core/templates/includes/{nav,pagination}.html
600 | # copiar o conteúdo
601 |
602 | mkdir -p bookstore/templates/bookstore
603 | touch bookstore/templates/bookstore/{book,author,publisher}_{list,detail,form,confirm_delete}.html
604 |
605 | touch bookstore/{forms,views}.py
606 | ```
607 |
608 |
609 | > Editar `bookstore/urls.py`
610 |
611 | ```python
612 | from django.urls import include, path
613 | from rest_framework import routers
614 |
615 | from bookstore import views as v
616 | from bookstore.api_drf.viewsets import (
617 | AuthorViewSet,
618 | BookViewSet,
619 | PublisherViewSet
620 | )
621 |
622 | app_name = 'bookstore'
623 |
624 | router = routers.DefaultRouter()
625 |
626 | router.register(r'authors', AuthorViewSet)
627 | router.register(r'publishers', PublisherViewSet)
628 | router.register(r'books', BookViewSet)
629 |
630 | authors_urlpatterns = [
631 | path('', v.AuthorListView.as_view(), name='author_list'),
632 | path('/', v.AuthorDetailView.as_view(), name='author_detail'),
633 | path('create/', v.AuthorCreateView.as_view(), name='author_create'),
634 | path('/update/', v.AuthorUpdateView.as_view(), name='author_update'),
635 | path('/delete/', v.AuthorDeleteView.as_view(), name='author_delete'),
636 | ]
637 |
638 | publishers_urlpatterns = [
639 | path('', v.PublisherListView.as_view(), name='publisher_list'),
640 | path('/', v.PublisherDetailView.as_view(), name='publisher_detail'),
641 | path('create/', v.PublisherCreateView.as_view(), name='publisher_create'),
642 | path('/update/', v.PublisherUpdateView.as_view(), name='publisher_update'),
643 | path('/delete/', v.PublisherDeleteView.as_view(), name='publisher_delete'),
644 | ]
645 |
646 | books_urlpatterns = [
647 | path('', v.BookListView.as_view(), name='book_list'),
648 | path('/', v.BookDetailView.as_view(), name='book_detail'),
649 | path('create/', v.BookCreateView.as_view(), name='book_create'),
650 | path('/update/', v.BookUpdateView.as_view(), name='book_update'),
651 | path('/delete/', v.BookDeleteView.as_view(), name='book_delete'),
652 | ]
653 |
654 | urlpatterns = [
655 | path('api/v1/bookstore/', include(router.urls)),
656 | path('author/', include(authors_urlpatterns)),
657 | path('publisher/', include(publishers_urlpatterns)),
658 | path('book/', include(books_urlpatterns)),
659 | ]
660 |
661 | ```
662 |
663 | > Editar `bookstore/views.py`
664 |
665 | ```python
666 | from django.urls import reverse_lazy
667 | from django.views.generic import (
668 | CreateView,
669 | DeleteView,
670 | DetailView,
671 | ListView,
672 | UpdateView
673 | )
674 |
675 | from .forms import AuthorForm, BookForm, PublisherForm
676 | from .models import Author, Book, Publisher
677 |
678 |
679 | class AuthorListView(ListView):
680 | model = Author
681 | paginate_by = 20
682 |
683 |
684 | class AuthorDetailView(DetailView):
685 | model = Author
686 |
687 |
688 | class AuthorCreateView(CreateView):
689 | model = Author
690 | form_class = AuthorForm
691 |
692 |
693 | class AuthorUpdateView(UpdateView):
694 | model = Author
695 | form_class = AuthorForm
696 |
697 |
698 | class AuthorDeleteView(DeleteView):
699 | model = Author
700 | success_url = reverse_lazy('bookstore:author_list')
701 |
702 |
703 | class PublisherListView(ListView):
704 | model = Publisher
705 | paginate_by = 20
706 |
707 |
708 | class PublisherDetailView(DetailView):
709 | model = Publisher
710 |
711 |
712 | class PublisherCreateView(CreateView):
713 | model = Publisher
714 | form_class = PublisherForm
715 |
716 |
717 | class PublisherUpdateView(UpdateView):
718 | model = Publisher
719 | form_class = PublisherForm
720 |
721 |
722 | class PublisherDeleteView(DeleteView):
723 | model = Publisher
724 | success_url = reverse_lazy('bookstore:publisher_list')
725 |
726 |
727 | class BookListView(ListView):
728 | model = Book
729 | paginate_by = 20
730 |
731 |
732 | class BookDetailView(DetailView):
733 | model = Book
734 |
735 |
736 | class BookCreateView(CreateView):
737 | model = Book
738 | form_class = BookForm
739 |
740 |
741 | class BookUpdateView(UpdateView):
742 | model = Book
743 | form_class = BookForm
744 |
745 |
746 | class BookDeleteView(DeleteView):
747 | model = Book
748 | success_url = reverse_lazy('bookstore:book_list')
749 |
750 | ```
751 |
752 |
753 | > Editar `bookstore/forms.py`
754 |
755 | ```python
756 | from django import forms
757 |
758 | from .models import Author, Book, Publisher
759 |
760 |
761 | class _BaseModelForm(forms.ModelForm):
762 | required_css_class = 'required'
763 |
764 | def __init__(self, *args, **kwargs):
765 | super(_BaseModelForm, self).__init__(*args, **kwargs)
766 | for field_name, field in self.fields.items():
767 | field.widget.attrs['class'] = 'form-control'
768 |
769 |
770 | class AuthorForm(_BaseModelForm):
771 |
772 | class Meta:
773 | model = Author
774 | fields = '__all__'
775 |
776 |
777 | class PublisherForm(_BaseModelForm):
778 |
779 | class Meta:
780 | model = Publisher
781 | fields = '__all__'
782 |
783 |
784 | class BookForm(_BaseModelForm):
785 |
786 | class Meta:
787 | model = Book
788 | fields = '__all__'
789 | ```
790 |
791 | > Editar `bookstore/models.py`
792 |
793 | ```python
794 | class Author(models.Model):
795 |
796 | def get_absolute_url(self):
797 | return reverse_lazy('bookstore:author_detail', kwargs={'pk': self.pk})
798 |
799 |
800 | class Publisher(models.Model):
801 |
802 | def get_absolute_url(self):
803 | return reverse_lazy('bookstore:publisher_detail', kwargs={'pk': self.pk})
804 |
805 |
806 | class Book(models.Model):
807 |
808 | def get_absolute_url(self):
809 | return reverse_lazy('bookstore:book_detail', kwargs={'pk': self.pk})
810 |
811 | ```
812 |
813 | > Editar os templates
814 |
815 |
--------------------------------------------------------------------------------