├── LICENSE
├── Readme.md
├── ch2-library-app-and-api
├── Pipfile
├── Pipfile.lock
├── api
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── books
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── books
│ │ │ └── book_list.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
└── manage.py
├── ch3-todo-api
├── Pipfile.lock
└── backend
│ ├── Pipfile
│ ├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│ ├── db.sqlite3
│ ├── manage.py
│ └── todos
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch4-todo-api-frontend
└── backend
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│ ├── db.sqlite3
│ ├── manage.py
│ └── todos
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch5-blog-api
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
└── posts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch6-blog-api-permissions
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
└── posts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── permissions.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch7-user-authentication
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
└── posts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── permissions.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch8-viewsets-and-routers
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
└── posts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── permissions.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── ch9-schemas-and-documentation
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
├── openapi-schema.yml
└── posts
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── permissions.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── cover.jpg
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 William S. Vincent
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | The official source code for [Django for APIs](https://djangoforapis.com). Available as an [ebook](https://gum.co/EzsI) or [paperback](https://www.amazon.com/dp/1735467227/?tag=wsvincent-20).
2 |
3 | 
4 |
5 | If you have the 3.0 version, please refer to [this repo for the source code](https://github.com/wsvincent/djangoforapis_30).
6 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 |
12 | [requires]
13 | python_version = "3.8"
14 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "d50f113a63968a27a3bb3975146f499e12e0e53154a5f727aef7fd473504f888"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "django": {
28 | "hashes": [
29 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
30 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
31 | ],
32 | "index": "pypi",
33 | "version": "==3.1"
34 | },
35 | "djangorestframework": {
36 | "hashes": [
37 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
38 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
39 | ],
40 | "index": "pypi",
41 | "version": "==3.11.0"
42 | },
43 | "pytz": {
44 | "hashes": [
45 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
46 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
47 | ],
48 | "version": "==2020.1"
49 | },
50 | "sqlparse": {
51 | "hashes": [
52 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
53 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
54 | ],
55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
56 | "version": "==0.3.1"
57 | }
58 | },
59 | "develop": {}
60 | }
61 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/api/__init__.py
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ApiConfig(AppConfig):
5 | name = 'api'
6 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/api/migrations/__init__.py
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from books.models import Book
3 |
4 |
5 | class BookSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Book
8 | fields = ('title', 'subtitle', 'author', 'isbn')
9 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import BookAPIView
3 |
4 | urlpatterns = [
5 | path('', BookAPIView.as_view()),
6 | ]
7 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/api/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from books.models import Book
3 | from .serializers import BookSerializer
4 |
5 |
6 | class BookAPIView(generics.ListAPIView):
7 | queryset = Book.objects.all()
8 | serializer_class = BookSerializer
9 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/books/__init__.py
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Book
3 |
4 | admin.site.register(Book)
5 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BooksConfig(AppConfig):
5 | name = 'books'
6 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-28 13:47
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Book',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('title', models.CharField(max_length=250)),
19 | ('subtitle', models.CharField(max_length=250)),
20 | ('author', models.CharField(max_length=100)),
21 | ('isbn', models.CharField(max_length=13)),
22 | ],
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/books/migrations/__init__.py
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | class Book(models.Model):
4 | title = models.CharField(max_length=250)
5 | subtitle = models.CharField(max_length=250)
6 | author = models.CharField(max_length=100)
7 | isbn = models.CharField(max_length=13)
8 |
9 | def __str__(self):
10 | return self.title
11 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/templates/books/book_list.html:
--------------------------------------------------------------------------------
1 |
All books
2 | {% for book in object_list %}
3 |
4 | - Title: {{ book.title }}
5 | - Subtitle: {{ book.subtitle }}
6 | - Author: {{ book.author }}
7 | - ISBN: {{ book.isbn }}
8 |
9 | {% endfor %}
10 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import BookListView
3 |
4 | urlpatterns = [
5 | path('', BookListView.as_view(), name='home'),
6 | ]
7 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/books/views.py:
--------------------------------------------------------------------------------
1 | from django.views.generic import ListView
2 | from .models import Book
3 |
4 |
5 | class BookListView(ListView):
6 | model = Book
7 | template_name = 'book_list.html'
8 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/config/__init__.py
--------------------------------------------------------------------------------
/ch2-library-app-and-api/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/config/settings.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
4 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
5 |
6 |
7 | # Quick-start development settings - unsuitable for production
8 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
9 |
10 | # SECURITY WARNING: keep the secret key used in production secret!
11 | SECRET_KEY = 'qnhe=6s_paq+kd_#mvyi!&6xn!&o&0m)#i!18u-#w^8n9%lwj%'
12 |
13 | # SECURITY WARNING: don't run with debug turned on in production!
14 | DEBUG = True
15 |
16 | ALLOWED_HOSTS = []
17 |
18 |
19 | # Application definition
20 |
21 | INSTALLED_APPS = [
22 | 'django.contrib.admin',
23 | 'django.contrib.auth',
24 | 'django.contrib.contenttypes',
25 | 'django.contrib.sessions',
26 | 'django.contrib.messages',
27 | 'django.contrib.staticfiles',
28 |
29 | # 3rd party
30 | 'rest_framework', # new
31 |
32 | # Local
33 | 'books',
34 | 'api', # new
35 | ]
36 |
37 | MIDDLEWARE = [
38 | 'django.middleware.security.SecurityMiddleware',
39 | 'django.contrib.sessions.middleware.SessionMiddleware',
40 | 'django.middleware.common.CommonMiddleware',
41 | 'django.middleware.csrf.CsrfViewMiddleware',
42 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
43 | 'django.contrib.messages.middleware.MessageMiddleware',
44 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
45 | ]
46 |
47 | ROOT_URLCONF = 'config.urls'
48 |
49 | TEMPLATES = [
50 | {
51 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
52 | 'DIRS': [],
53 | 'APP_DIRS': True,
54 | 'OPTIONS': {
55 | 'context_processors': [
56 | 'django.template.context_processors.debug',
57 | 'django.template.context_processors.request',
58 | 'django.contrib.auth.context_processors.auth',
59 | 'django.contrib.messages.context_processors.messages',
60 | ],
61 | },
62 | },
63 | ]
64 |
65 | WSGI_APPLICATION = 'config.wsgi.application'
66 |
67 |
68 | # Database
69 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
70 |
71 | DATABASES = {
72 | 'default': {
73 | 'ENGINE': 'django.db.backends.sqlite3',
74 | 'NAME': BASE_DIR / 'db.sqlite3',
75 | }
76 | }
77 |
78 |
79 | # Password validation
80 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
81 |
82 | AUTH_PASSWORD_VALIDATORS = [
83 | {
84 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
85 | },
86 | {
87 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
88 | },
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
91 | },
92 | {
93 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
94 | },
95 | ]
96 |
97 |
98 | # Internationalization
99 | # https://docs.djangoproject.com/en/dev/topics/i18n/
100 |
101 | LANGUAGE_CODE = 'en-us'
102 |
103 | TIME_ZONE = 'UTC'
104 |
105 | USE_I18N = True
106 |
107 | USE_L10N = True
108 |
109 | USE_TZ = True
110 |
111 |
112 | # Static files (CSS, JavaScript, Images)
113 | # https://docs.djangoproject.com/en/dev/howto/static-files/
114 |
115 | STATIC_URL = '/static/'
116 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path, include # new
3 |
4 | urlpatterns = [
5 | path('admin/', admin.site.urls),
6 | path('api/', include('api.urls')), # new
7 | path('', include('books.urls')), # new
8 | ]
9 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch2-library-app-and-api/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch2-library-app-and-api/db.sqlite3
--------------------------------------------------------------------------------
/ch2-library-app-and-api/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', 'config.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 |
--------------------------------------------------------------------------------
/ch3-todo-api/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "e56ad8b23f3e562a4287d55b3ec7789ff5b63f5d12f82a1b47b358d16b8402d3"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "django": {
28 | "hashes": [
29 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
30 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
31 | ],
32 | "index": "pypi",
33 | "version": "==3.1"
34 | },
35 | "django-cors-headers": {
36 | "hashes": [
37 | "sha256:5240062ef0b16668ce8a5f43324c388d65f5439e1a30e22c38684d5ddaff0d15",
38 | "sha256:f5218f2f0bb1210563ff87687afbf10786e080d8494a248e705507ebd92d7153"
39 | ],
40 | "index": "pypi",
41 | "version": "==3.4.0"
42 | },
43 | "djangorestframework": {
44 | "hashes": [
45 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
46 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
47 | ],
48 | "index": "pypi",
49 | "version": "==3.11.0"
50 | },
51 | "pytz": {
52 | "hashes": [
53 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
54 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
55 | ],
56 | "version": "==2020.1"
57 | },
58 | "sqlparse": {
59 | "hashes": [
60 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
61 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
62 | ],
63 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
64 | "version": "==0.3.1"
65 | }
66 | },
67 | "develop": {}
68 | }
69 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "==3.1rc1"
10 | djangorestframework = "==3.11.0"
11 | django-cors-headers = "==3.4.0"
12 |
13 | [requires]
14 | python_version = "3.8"
15 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch3-todo-api/backend/config/__init__.py
--------------------------------------------------------------------------------
/ch3-todo-api/backend/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/config/settings.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
4 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
5 |
6 |
7 | # Quick-start development settings - unsuitable for production
8 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
9 |
10 | # SECURITY WARNING: keep the secret key used in production secret!
11 | SECRET_KEY = 'y2kf+s*_9sws0agzq%gx4mw6d53xm&n^j!wf3e)=uf_^6m(@z@'
12 |
13 | # SECURITY WARNING: don't run with debug turned on in production!
14 | DEBUG = True
15 |
16 | ALLOWED_HOSTS = []
17 |
18 |
19 | # Application definition
20 |
21 | INSTALLED_APPS = [
22 | 'django.contrib.admin',
23 | 'django.contrib.auth',
24 | 'django.contrib.contenttypes',
25 | 'django.contrib.sessions',
26 | 'django.contrib.messages',
27 | 'django.contrib.staticfiles',
28 |
29 | # 3rd party
30 | 'rest_framework', # new
31 | 'corsheaders', # new
32 |
33 | # Local
34 | 'todos', # new
35 | ]
36 |
37 | MIDDLEWARE = [
38 | 'django.middleware.security.SecurityMiddleware',
39 | 'django.contrib.sessions.middleware.SessionMiddleware',
40 | 'corsheaders.middleware.CorsMiddleware', # new
41 | 'django.middleware.common.CommonMiddleware',
42 | 'django.middleware.csrf.CsrfViewMiddleware',
43 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
44 | 'django.contrib.messages.middleware.MessageMiddleware',
45 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
46 | ]
47 |
48 | ROOT_URLCONF = 'config.urls'
49 |
50 | TEMPLATES = [
51 | {
52 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
53 | 'DIRS': [],
54 | 'APP_DIRS': True,
55 | 'OPTIONS': {
56 | 'context_processors': [
57 | 'django.template.context_processors.debug',
58 | 'django.template.context_processors.request',
59 | 'django.contrib.auth.context_processors.auth',
60 | 'django.contrib.messages.context_processors.messages',
61 | ],
62 | },
63 | },
64 | ]
65 |
66 | WSGI_APPLICATION = 'config.wsgi.application'
67 |
68 |
69 | # Database
70 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
71 |
72 | DATABASES = {
73 | 'default': {
74 | 'ENGINE': 'django.db.backends.sqlite3',
75 | 'NAME': BASE_DIR / 'db.sqlite3',
76 | }
77 | }
78 |
79 |
80 | # Password validation
81 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
82 |
83 | AUTH_PASSWORD_VALIDATORS = [
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
89 | },
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
92 | },
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
95 | },
96 | ]
97 |
98 |
99 | # Internationalization
100 | # https://docs.djangoproject.com/en/dev/topics/i18n/
101 |
102 | LANGUAGE_CODE = 'en-us'
103 |
104 | TIME_ZONE = 'UTC'
105 |
106 | USE_I18N = True
107 |
108 | USE_L10N = True
109 |
110 | USE_TZ = True
111 |
112 |
113 | # Static files (CSS, JavaScript, Images)
114 | # https://docs.djangoproject.com/en/dev/howto/static-files/
115 |
116 | STATIC_URL = '/static/'
117 |
118 | REST_FRAMEWORK = {
119 | 'DEFAULT_PERMISSION_CLASSES': [
120 | 'rest_framework.permissions.AllowAny',
121 | ]
122 | }
123 |
124 | CORS_ORIGIN_WHITELIST = (
125 | 'http://localhost:3000',
126 | 'http://localhost:8000',
127 | )
128 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path # new
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/', include('todos.urls')), # new
8 | ]
9 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch3-todo-api/backend/db.sqlite3
--------------------------------------------------------------------------------
/ch3-todo-api/backend/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', 'config.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 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch3-todo-api/backend/todos/__init__.py
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Todo
3 |
4 | admin.site.register(Todo)
5 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class TodosConfig(AppConfig):
5 | name = 'todos'
6 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-28 15:01
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Todo',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('title', models.CharField(max_length=200)),
19 | ('body', models.TextField()),
20 | ],
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch3-todo-api/backend/todos/migrations/__init__.py
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Todo(models.Model):
5 | title = models.CharField(max_length=200)
6 | body = models.TextField()
7 |
8 | def __str__(self):
9 | return self.title
10 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Todo
3 |
4 |
5 | class TodoSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Todo
8 | fields = ('id', 'title', 'body',)
9 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from .models import Todo
3 |
4 |
5 | class TodoModelTest(TestCase):
6 |
7 | @classmethod
8 | def setUpTestData(cls):
9 | Todo.objects.create(title='first todo', body='a body here')
10 |
11 | def test_title_content(self):
12 | todo = Todo.objects.get(id=1)
13 | expected_object_name = f'{todo.title}'
14 | self.assertEqual(expected_object_name, 'first todo')
15 |
16 | def test_body_content(self):
17 | todo = Todo.objects.get(id=1)
18 | expected_object_name = f'{todo.body}'
19 | self.assertEqual(expected_object_name, 'a body here')
20 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import ListTodo, DetailTodo
3 |
4 | urlpatterns = [
5 | path('/', DetailTodo.as_view()),
6 | path('', ListTodo.as_view()),
7 | ]
8 |
--------------------------------------------------------------------------------
/ch3-todo-api/backend/todos/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from .models import Todo
3 | from .serializers import TodoSerializer
4 |
5 |
6 | class ListTodo(generics.ListAPIView):
7 | queryset = Todo.objects.all()
8 | serializer_class = TodoSerializer
9 |
10 |
11 | class DetailTodo(generics.RetrieveAPIView):
12 | queryset = Todo.objects.all()
13 | serializer_class = TodoSerializer
14 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 | django-cors-headers = "==3.4.0"
12 |
13 | [requires]
14 | python_version = "3.8"
15 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "e56ad8b23f3e562a4287d55b3ec7789ff5b63f5d12f82a1b47b358d16b8402d3"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "django": {
28 | "hashes": [
29 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
30 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
31 | ],
32 | "index": "pypi",
33 | "version": "==3.1"
34 | },
35 | "django-cors-headers": {
36 | "hashes": [
37 | "sha256:5240062ef0b16668ce8a5f43324c388d65f5439e1a30e22c38684d5ddaff0d15",
38 | "sha256:f5218f2f0bb1210563ff87687afbf10786e080d8494a248e705507ebd92d7153"
39 | ],
40 | "index": "pypi",
41 | "version": "==3.4.0"
42 | },
43 | "djangorestframework": {
44 | "hashes": [
45 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
46 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
47 | ],
48 | "index": "pypi",
49 | "version": "==3.11.0"
50 | },
51 | "pytz": {
52 | "hashes": [
53 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
54 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
55 | ],
56 | "version": "==2020.1"
57 | },
58 | "sqlparse": {
59 | "hashes": [
60 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
61 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
62 | ],
63 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
64 | "version": "==0.3.1"
65 | }
66 | },
67 | "develop": {}
68 | }
69 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch4-todo-api-frontend/backend/config/__init__.py
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/config/settings.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
4 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
5 |
6 |
7 | # Quick-start development settings - unsuitable for production
8 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
9 |
10 | # SECURITY WARNING: keep the secret key used in production secret!
11 | SECRET_KEY = 'y2kf+s*_9sws0agzq%gx4mw6d53xm&n^j!wf3e)=uf_^6m(@z@'
12 |
13 | # SECURITY WARNING: don't run with debug turned on in production!
14 | DEBUG = True
15 |
16 | ALLOWED_HOSTS = []
17 |
18 |
19 | # Application definition
20 |
21 | INSTALLED_APPS = [
22 | 'django.contrib.admin',
23 | 'django.contrib.auth',
24 | 'django.contrib.contenttypes',
25 | 'django.contrib.sessions',
26 | 'django.contrib.messages',
27 | 'django.contrib.staticfiles',
28 |
29 | # 3rd party
30 | 'rest_framework', # new
31 | 'corsheaders', # new
32 |
33 | # Local
34 | 'todos.apps.TodosConfig', # new
35 | ]
36 |
37 | MIDDLEWARE = [
38 | 'django.middleware.security.SecurityMiddleware',
39 | 'django.contrib.sessions.middleware.SessionMiddleware',
40 | 'corsheaders.middleware.CorsMiddleware', # new
41 | 'django.middleware.common.CommonMiddleware',
42 | 'django.middleware.csrf.CsrfViewMiddleware',
43 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
44 | 'django.contrib.messages.middleware.MessageMiddleware',
45 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
46 | ]
47 |
48 | ROOT_URLCONF = 'config.urls'
49 |
50 | TEMPLATES = [
51 | {
52 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
53 | 'DIRS': [],
54 | 'APP_DIRS': True,
55 | 'OPTIONS': {
56 | 'context_processors': [
57 | 'django.template.context_processors.debug',
58 | 'django.template.context_processors.request',
59 | 'django.contrib.auth.context_processors.auth',
60 | 'django.contrib.messages.context_processors.messages',
61 | ],
62 | },
63 | },
64 | ]
65 |
66 | WSGI_APPLICATION = 'config.wsgi.application'
67 |
68 |
69 | # Database
70 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
71 |
72 | DATABASES = {
73 | 'default': {
74 | 'ENGINE': 'django.db.backends.sqlite3',
75 | 'NAME': BASE_DIR / 'db.sqlite3',
76 | }
77 | }
78 |
79 |
80 | # Password validation
81 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
82 |
83 | AUTH_PASSWORD_VALIDATORS = [
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
89 | },
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
92 | },
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
95 | },
96 | ]
97 |
98 |
99 | # Internationalization
100 | # https://docs.djangoproject.com/en/dev/topics/i18n/
101 |
102 | LANGUAGE_CODE = 'en-us'
103 |
104 | TIME_ZONE = 'UTC'
105 |
106 | USE_I18N = True
107 |
108 | USE_L10N = True
109 |
110 | USE_TZ = True
111 |
112 |
113 | # Static files (CSS, JavaScript, Images)
114 | # https://docs.djangoproject.com/en/dev/howto/static-files/
115 |
116 | STATIC_URL = '/static/'
117 |
118 | REST_FRAMEWORK = {
119 | 'DEFAULT_PERMISSION_CLASSES': [
120 | 'rest_framework.permissions.AllowAny',
121 | ]
122 | }
123 |
124 | CORS_ORIGIN_WHITELIST = (
125 | 'http://localhost:3000',
126 | 'http://localhost:8000',
127 | )
128 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path # new
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/', include('todos.urls')), # new
8 | ]
9 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch4-todo-api-frontend/backend/db.sqlite3
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/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', 'config.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 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch4-todo-api-frontend/backend/todos/__init__.py
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Todo
3 |
4 | admin.site.register(Todo)
5 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class TodosConfig(AppConfig):
5 | name = 'todos'
6 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-28 15:01
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Todo',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('title', models.CharField(max_length=200)),
19 | ('body', models.TextField()),
20 | ],
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch4-todo-api-frontend/backend/todos/migrations/__init__.py
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Todo(models.Model):
5 | title = models.CharField(max_length=200)
6 | body = models.TextField()
7 |
8 | def __str__(self):
9 | return self.title
10 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Todo
3 |
4 |
5 | class TodoSerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Todo
8 | fields = ('id', 'title', 'body',)
9 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from .models import Todo
3 |
4 |
5 | class TodoModelTest(TestCase):
6 |
7 | @classmethod
8 | def setUpTestData(cls):
9 | Todo.objects.create(title='first todo', body='a body here')
10 |
11 | def test_title_content(self):
12 | todo = Todo.objects.get(id=1)
13 | expected_object_name = f'{todo.title}'
14 | self.assertEqual(expected_object_name, 'first todo')
15 |
16 | def test_body_content(self):
17 | todo = Todo.objects.get(id=1)
18 | expected_object_name = f'{todo.body}'
19 | self.assertEqual(expected_object_name, 'a body here')
20 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import ListTodo, DetailTodo
3 |
4 | urlpatterns = [
5 | path('/', DetailTodo.as_view()),
6 | path('', ListTodo.as_view()),
7 | ]
8 |
--------------------------------------------------------------------------------
/ch4-todo-api-frontend/backend/todos/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from .models import Todo
3 | from .serializers import TodoSerializer
4 |
5 |
6 | class ListTodo(generics.ListAPIView):
7 | queryset = Todo.objects.all()
8 | serializer_class = TodoSerializer
9 |
10 |
11 | class DetailTodo(generics.RetrieveAPIView):
12 | queryset = Todo.objects.all()
13 | serializer_class = TodoSerializer
14 |
--------------------------------------------------------------------------------
/ch5-blog-api/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 |
12 | [requires]
13 | python_version = "3.8"
14 |
--------------------------------------------------------------------------------
/ch5-blog-api/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "d50f113a63968a27a3bb3975146f499e12e0e53154a5f727aef7fd473504f888"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "django": {
28 | "hashes": [
29 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
30 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
31 | ],
32 | "index": "pypi",
33 | "version": "==3.1"
34 | },
35 | "djangorestframework": {
36 | "hashes": [
37 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
38 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
39 | ],
40 | "index": "pypi",
41 | "version": "==3.11.0"
42 | },
43 | "pytz": {
44 | "hashes": [
45 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
46 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
47 | ],
48 | "version": "==2020.1"
49 | },
50 | "sqlparse": {
51 | "hashes": [
52 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
53 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
54 | ],
55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
56 | "version": "==0.3.1"
57 | }
58 | },
59 | "develop": {}
60 | }
61 |
--------------------------------------------------------------------------------
/ch5-blog-api/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch5-blog-api/config/__init__.py
--------------------------------------------------------------------------------
/ch5-blog-api/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch5-blog-api/config/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for config project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1rc1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/dev/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '5kji9d4+u2=pw4k2bh+v4pt7(57)$hsns-g_ma0oskgf*aa16&'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 |
41 | # 3rd-party apps
42 | 'rest_framework', # new
43 |
44 | # Local
45 | 'posts', # new
46 | ]
47 |
48 | MIDDLEWARE = [
49 | 'django.middleware.security.SecurityMiddleware',
50 | 'django.contrib.sessions.middleware.SessionMiddleware',
51 | 'django.middleware.common.CommonMiddleware',
52 | 'django.middleware.csrf.CsrfViewMiddleware',
53 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'config.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | },
73 | },
74 | ]
75 |
76 | WSGI_APPLICATION = 'config.wsgi.application'
77 |
78 |
79 | # Database
80 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
81 |
82 | DATABASES = {
83 | 'default': {
84 | 'ENGINE': 'django.db.backends.sqlite3',
85 | 'NAME': BASE_DIR / 'db.sqlite3',
86 | }
87 | }
88 |
89 |
90 | # Password validation
91 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
92 |
93 | AUTH_PASSWORD_VALIDATORS = [
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
96 | },
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
99 | },
100 | {
101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
102 | },
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
105 | },
106 | ]
107 |
108 |
109 | # Internationalization
110 | # https://docs.djangoproject.com/en/dev/topics/i18n/
111 |
112 | LANGUAGE_CODE = 'en-us'
113 |
114 | TIME_ZONE = 'UTC'
115 |
116 | USE_I18N = True
117 |
118 | USE_L10N = True
119 |
120 | USE_TZ = True
121 |
122 |
123 | # Static files (CSS, JavaScript, Images)
124 | # https://docs.djangoproject.com/en/dev/howto/static-files/
125 |
126 | STATIC_URL = '/static/'
127 |
128 | REST_FRAMEWORK = {
129 | 'DEFAULT_PERMISSION_CLASSES': [
130 | 'rest_framework.permissions.AllowAny',
131 | ]
132 | }
133 |
--------------------------------------------------------------------------------
/ch5-blog-api/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path # new
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/v1/', include('posts.urls')), # new
8 | ]
9 |
--------------------------------------------------------------------------------
/ch5-blog-api/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch5-blog-api/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch5-blog-api/db.sqlite3
--------------------------------------------------------------------------------
/ch5-blog-api/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', 'config.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 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch5-blog-api/posts/__init__.py
--------------------------------------------------------------------------------
/ch5-blog-api/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | admin.site.register(Post)
5 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-29 17:28
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=50)),
22 | ('body', models.TextField()),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch5-blog-api/posts/migrations/__init__.py
--------------------------------------------------------------------------------
/ch5-blog-api/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Post(models.Model):
6 | author = models.ForeignKey(User, on_delete=models.CASCADE)
7 | title = models.CharField(max_length=50)
8 | body = models.TextField()
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return self.title
14 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Post
3 |
4 |
5 | class PostSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | fields = ('id', 'author', 'title', 'body', 'created_at',)
9 | model = Post
10 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth.models import User
3 | from .models import Post
4 |
5 |
6 | class BlogTests(TestCase):
7 |
8 | @classmethod
9 | def setUpTestData(cls):
10 | # Create a user
11 | testuser1 = User.objects.create_user(
12 | username='testuser1', password='abc123')
13 | testuser1.save()
14 |
15 | # Create a blog post
16 | test_post = Post.objects.create(
17 | author=testuser1, title='Blog title', body='Body content...')
18 | test_post.save()
19 |
20 | def test_blog_content(self):
21 | post = Post.objects.get(id=1)
22 | author = f'{post.author}'
23 | title = f'{post.title}'
24 | body = f'{post.body}'
25 | self.assertEqual(author, 'testuser1')
26 | self.assertEqual(title, 'Blog title')
27 | self.assertEqual(body, 'Body content...')
28 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import PostList, PostDetail
3 |
4 | urlpatterns = [
5 | path('/', PostDetail.as_view()),
6 | path('', PostList.as_view()),
7 | ]
8 |
--------------------------------------------------------------------------------
/ch5-blog-api/posts/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from .models import Post
3 | from .serializers import PostSerializer
4 |
5 |
6 | class PostList(generics.ListCreateAPIView):
7 | queryset = Post.objects.all()
8 | serializer_class = PostSerializer
9 |
10 |
11 | class PostDetail(generics.RetrieveUpdateDestroyAPIView):
12 | queryset = Post.objects.all()
13 | serializer_class = PostSerializer
14 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 |
12 | [requires]
13 | python_version = "3.8"
14 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "d50f113a63968a27a3bb3975146f499e12e0e53154a5f727aef7fd473504f888"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "django": {
28 | "hashes": [
29 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
30 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
31 | ],
32 | "index": "pypi",
33 | "version": "==3.1"
34 | },
35 | "djangorestframework": {
36 | "hashes": [
37 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
38 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
39 | ],
40 | "index": "pypi",
41 | "version": "==3.11.0"
42 | },
43 | "pytz": {
44 | "hashes": [
45 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
46 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
47 | ],
48 | "version": "==2020.1"
49 | },
50 | "sqlparse": {
51 | "hashes": [
52 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
53 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
54 | ],
55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
56 | "version": "==0.3.1"
57 | }
58 | },
59 | "develop": {}
60 | }
61 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch6-blog-api-permissions/config/__init__.py
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/config/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for config project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1rc1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/dev/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '5kji9d4+u2=pw4k2bh+v4pt7(57)$hsns-g_ma0oskgf*aa16&'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 |
41 | # 3rd-party apps
42 | 'rest_framework', # new
43 |
44 | # Local
45 | 'posts', # new
46 | ]
47 |
48 | MIDDLEWARE = [
49 | 'django.middleware.security.SecurityMiddleware',
50 | 'django.contrib.sessions.middleware.SessionMiddleware',
51 | 'django.middleware.common.CommonMiddleware',
52 | 'django.middleware.csrf.CsrfViewMiddleware',
53 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'config.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | },
73 | },
74 | ]
75 |
76 | WSGI_APPLICATION = 'config.wsgi.application'
77 |
78 |
79 | # Database
80 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
81 |
82 | DATABASES = {
83 | 'default': {
84 | 'ENGINE': 'django.db.backends.sqlite3',
85 | 'NAME': BASE_DIR / 'db.sqlite3',
86 | }
87 | }
88 |
89 |
90 | # Password validation
91 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
92 |
93 | AUTH_PASSWORD_VALIDATORS = [
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
96 | },
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
99 | },
100 | {
101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
102 | },
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
105 | },
106 | ]
107 |
108 |
109 | # Internationalization
110 | # https://docs.djangoproject.com/en/dev/topics/i18n/
111 |
112 | LANGUAGE_CODE = 'en-us'
113 |
114 | TIME_ZONE = 'UTC'
115 |
116 | USE_I18N = True
117 |
118 | USE_L10N = True
119 |
120 | USE_TZ = True
121 |
122 |
123 | # Static files (CSS, JavaScript, Images)
124 | # https://docs.djangoproject.com/en/dev/howto/static-files/
125 |
126 | STATIC_URL = '/static/'
127 |
128 | REST_FRAMEWORK = {
129 | 'DEFAULT_PERMISSION_CLASSES': [
130 | 'rest_framework.permissions.IsAuthenticated', # new
131 | ]
132 | }
133 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/v1/', include('posts.urls')),
8 | path('api-auth/', include('rest_framework.urls')), # new
9 | ]
10 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch6-blog-api-permissions/db.sqlite3
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/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', 'config.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 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch6-blog-api-permissions/posts/__init__.py
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | admin.site.register(Post)
5 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-29 17:28
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=50)),
22 | ('body', models.TextField()),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch6-blog-api-permissions/posts/migrations/__init__.py
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Post(models.Model):
6 | author = models.ForeignKey(User, on_delete=models.CASCADE)
7 | title = models.CharField(max_length=50)
8 | body = models.TextField()
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return self.title
14 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class IsAuthorOrReadOnly(permissions.BasePermission):
5 |
6 | def has_object_permission(self, request, view, obj):
7 | # Read-only permissions are allowed for any request
8 | if request.method in permissions.SAFE_METHODS:
9 | return True
10 |
11 | # Write permissions are only allowed to the author of a post
12 | return obj.author == request.user
13 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Post
3 |
4 |
5 | class PostSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | fields = ('id', 'author', 'title', 'body', 'created_at',)
9 | model = Post
10 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth.models import User
3 | from .models import Post
4 |
5 |
6 | class BlogTests(TestCase):
7 |
8 | @classmethod
9 | def setUpTestData(cls):
10 | # Create a user
11 | testuser1 = User.objects.create_user(
12 | username='testuser1', password='abc123')
13 | testuser1.save()
14 |
15 | # Create a blog post
16 | test_post = Post.objects.create(
17 | author=testuser1, title='Blog title', body='Body content...')
18 | test_post.save()
19 |
20 | def test_blog_content(self):
21 | post = Post.objects.get(id=1)
22 | author = f'{post.author}'
23 | title = f'{post.title}'
24 | body = f'{post.body}'
25 | self.assertEqual(author, 'testuser1')
26 | self.assertEqual(title, 'Blog title')
27 | self.assertEqual(body, 'Body content...')
28 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import PostList, PostDetail
3 |
4 | urlpatterns = [
5 | path('/', PostDetail.as_view()),
6 | path('', PostList.as_view()),
7 | ]
8 |
--------------------------------------------------------------------------------
/ch6-blog-api-permissions/posts/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from .models import Post
3 | from .permissions import IsAuthorOrReadOnly # new
4 | from .serializers import PostSerializer
5 |
6 |
7 | class PostList(generics.ListCreateAPIView):
8 | queryset = Post.objects.all()
9 | serializer_class = PostSerializer
10 |
11 |
12 | class PostDetail(generics.RetrieveUpdateDestroyAPIView):
13 | permission_classes = (IsAuthorOrReadOnly,) # new
14 | queryset = Post.objects.all()
15 | serializer_class = PostSerializer
16 |
--------------------------------------------------------------------------------
/ch7-user-authentication/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 | dj-rest-auth = "==1.1.0"
12 | django-allauth = "==0.42.0"
13 |
14 | [requires]
15 | python_version = "3.8"
16 |
--------------------------------------------------------------------------------
/ch7-user-authentication/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "fb3781b26021056fc12f3ac404b43c8a29828064dfd593b34b1c9b3e6a4af2a6"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "certifi": {
28 | "hashes": [
29 | "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
30 | "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
31 | ],
32 | "version": "==2020.6.20"
33 | },
34 | "chardet": {
35 | "hashes": [
36 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
37 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
38 | ],
39 | "version": "==3.0.4"
40 | },
41 | "defusedxml": {
42 | "hashes": [
43 | "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
44 | "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
45 | ],
46 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
47 | "version": "==0.6.0"
48 | },
49 | "dj-rest-auth": {
50 | "hashes": [
51 | "sha256:f058a822ffa8764dea58750d415d3a43f038fe79acd0f5dc473704393cc5d65d"
52 | ],
53 | "index": "pypi",
54 | "version": "==1.1.0"
55 | },
56 | "django": {
57 | "hashes": [
58 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
59 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
60 | ],
61 | "index": "pypi",
62 | "version": "==3.1"
63 | },
64 | "django-allauth": {
65 | "hashes": [
66 | "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30"
67 | ],
68 | "index": "pypi",
69 | "version": "==0.42.0"
70 | },
71 | "djangorestframework": {
72 | "hashes": [
73 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
74 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
75 | ],
76 | "index": "pypi",
77 | "version": "==3.11.0"
78 | },
79 | "idna": {
80 | "hashes": [
81 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
82 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
83 | ],
84 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
85 | "version": "==2.10"
86 | },
87 | "oauthlib": {
88 | "hashes": [
89 | "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
90 | "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
91 | ],
92 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
93 | "version": "==3.1.0"
94 | },
95 | "python3-openid": {
96 | "hashes": [
97 | "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
98 | "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"
99 | ],
100 | "version": "==3.2.0"
101 | },
102 | "pytz": {
103 | "hashes": [
104 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
105 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
106 | ],
107 | "version": "==2020.1"
108 | },
109 | "requests": {
110 | "hashes": [
111 | "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
112 | "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
113 | ],
114 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
115 | "version": "==2.24.0"
116 | },
117 | "requests-oauthlib": {
118 | "hashes": [
119 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
120 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
121 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
122 | ],
123 | "version": "==1.3.0"
124 | },
125 | "sqlparse": {
126 | "hashes": [
127 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
128 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
129 | ],
130 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
131 | "version": "==0.3.1"
132 | },
133 | "urllib3": {
134 | "hashes": [
135 | "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
136 | "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
137 | ],
138 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
139 | "version": "==1.25.10"
140 | }
141 | },
142 | "develop": {}
143 | }
144 |
--------------------------------------------------------------------------------
/ch7-user-authentication/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch7-user-authentication/config/__init__.py
--------------------------------------------------------------------------------
/ch7-user-authentication/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch7-user-authentication/config/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for config project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1rc1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/dev/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '5kji9d4+u2=pw4k2bh+v4pt7(57)$hsns-g_ma0oskgf*aa16&'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'django.contrib.sites', # new
41 |
42 | # 3rd-party apps
43 | 'rest_framework',
44 | 'rest_framework.authtoken',
45 | 'allauth', # new
46 | 'allauth.account', # new
47 | 'allauth.socialaccount', # new
48 | 'dj_rest_auth',
49 | 'dj_rest_auth.registration', # new
50 |
51 | # Local
52 | 'posts',
53 | ]
54 |
55 | MIDDLEWARE = [
56 | 'django.middleware.security.SecurityMiddleware',
57 | 'django.contrib.sessions.middleware.SessionMiddleware',
58 | 'django.middleware.common.CommonMiddleware',
59 | 'django.middleware.csrf.CsrfViewMiddleware',
60 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
61 | 'django.contrib.messages.middleware.MessageMiddleware',
62 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
63 | ]
64 |
65 | ROOT_URLCONF = 'config.urls'
66 |
67 | TEMPLATES = [
68 | {
69 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
70 | 'DIRS': [],
71 | 'APP_DIRS': True,
72 | 'OPTIONS': {
73 | 'context_processors': [
74 | 'django.template.context_processors.debug',
75 | 'django.template.context_processors.request',
76 | 'django.contrib.auth.context_processors.auth',
77 | 'django.contrib.messages.context_processors.messages',
78 | ],
79 | },
80 | },
81 | ]
82 |
83 | WSGI_APPLICATION = 'config.wsgi.application'
84 |
85 |
86 | # Database
87 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
88 |
89 | DATABASES = {
90 | 'default': {
91 | 'ENGINE': 'django.db.backends.sqlite3',
92 | 'NAME': BASE_DIR / 'db.sqlite3',
93 | }
94 | }
95 |
96 |
97 | # Password validation
98 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
99 |
100 | AUTH_PASSWORD_VALIDATORS = [
101 | {
102 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
103 | },
104 | {
105 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
106 | },
107 | {
108 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
109 | },
110 | {
111 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
112 | },
113 | ]
114 |
115 |
116 | # Internationalization
117 | # https://docs.djangoproject.com/en/dev/topics/i18n/
118 |
119 | LANGUAGE_CODE = 'en-us'
120 |
121 | TIME_ZONE = 'UTC'
122 |
123 | USE_I18N = True
124 |
125 | USE_L10N = True
126 |
127 | USE_TZ = True
128 |
129 |
130 | # Static files (CSS, JavaScript, Images)
131 | # https://docs.djangoproject.com/en/dev/howto/static-files/
132 |
133 | STATIC_URL = '/static/'
134 |
135 | REST_FRAMEWORK = {
136 | 'DEFAULT_PERMISSION_CLASSES': [
137 | 'rest_framework.permissions.IsAuthenticated',
138 | ],
139 | 'DEFAULT_AUTHENTICATION_CLASSES': [
140 | 'rest_framework.authentication.SessionAuthentication',
141 | 'rest_framework.authentication.TokenAuthentication', # new
142 | ],
143 | }
144 |
145 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # new
146 |
147 | SITE_ID = 1 # new
148 |
--------------------------------------------------------------------------------
/ch7-user-authentication/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/v1/', include('posts.urls')),
8 | path('api-auth/', include('rest_framework.urls')),
9 | path('api/v1/dj-rest-auth/', include('dj_rest_auth.urls')),
10 | path('api/v1/dj-rest-auth/registration/', # new
11 | include('dj_rest_auth.registration.urls')),
12 | ]
13 |
--------------------------------------------------------------------------------
/ch7-user-authentication/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch7-user-authentication/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch7-user-authentication/db.sqlite3
--------------------------------------------------------------------------------
/ch7-user-authentication/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', 'config.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 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch7-user-authentication/posts/__init__.py
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | admin.site.register(Post)
5 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-29 17:28
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=50)),
22 | ('body', models.TextField()),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch7-user-authentication/posts/migrations/__init__.py
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Post(models.Model):
6 | author = models.ForeignKey(User, on_delete=models.CASCADE)
7 | title = models.CharField(max_length=50)
8 | body = models.TextField()
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return self.title
14 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class IsAuthorOrReadOnly(permissions.BasePermission):
5 |
6 | def has_object_permission(self, request, view, obj):
7 | # Read-only permissions are allowed for any request
8 | if request.method in permissions.SAFE_METHODS:
9 | return True
10 |
11 | # Write permissions are only allowed to the author of a post
12 | return obj.author == request.user
13 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Post
3 |
4 |
5 | class PostSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | fields = ('id', 'author', 'title', 'body', 'created_at',)
9 | model = Post
10 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth.models import User
3 | from .models import Post
4 |
5 |
6 | class BlogTests(TestCase):
7 |
8 | @classmethod
9 | def setUpTestData(cls):
10 | # Create a user
11 | testuser1 = User.objects.create_user(
12 | username='testuser1', password='abc123')
13 | testuser1.save()
14 |
15 | # Create a blog post
16 | test_post = Post.objects.create(
17 | author=testuser1, title='Blog title', body='Body content...')
18 | test_post.save()
19 |
20 | def test_blog_content(self):
21 | post = Post.objects.get(id=1)
22 | author = f'{post.author}'
23 | title = f'{post.title}'
24 | body = f'{post.body}'
25 | self.assertEqual(author, 'testuser1')
26 | self.assertEqual(title, 'Blog title')
27 | self.assertEqual(body, 'Body content...')
28 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import PostList, PostDetail
3 |
4 | urlpatterns = [
5 | path('/', PostDetail.as_view()),
6 | path('', PostList.as_view()),
7 | ]
8 |
--------------------------------------------------------------------------------
/ch7-user-authentication/posts/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from .models import Post
3 | from .permissions import IsAuthorOrReadOnly # new
4 | from .serializers import PostSerializer
5 |
6 |
7 | class PostList(generics.ListCreateAPIView):
8 | queryset = Post.objects.all()
9 | serializer_class = PostSerializer
10 |
11 |
12 | class PostDetail(generics.RetrieveUpdateDestroyAPIView):
13 | permission_classes = (IsAuthorOrReadOnly,) # new
14 | queryset = Post.objects.all()
15 | serializer_class = PostSerializer
16 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 | dj-rest-auth = "==1.1.0"
12 | django-allauth = "~=0.42.0"
13 |
14 | [requires]
15 | python_version = "3.8"
16 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "1ea139dc2bd198cb5e43732c8ab669d8f2c9444f61123cb10ce1b4a76961269b"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "certifi": {
28 | "hashes": [
29 | "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
30 | "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
31 | ],
32 | "version": "==2020.6.20"
33 | },
34 | "chardet": {
35 | "hashes": [
36 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
37 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
38 | ],
39 | "version": "==3.0.4"
40 | },
41 | "defusedxml": {
42 | "hashes": [
43 | "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
44 | "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
45 | ],
46 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
47 | "version": "==0.6.0"
48 | },
49 | "dj-rest-auth": {
50 | "hashes": [
51 | "sha256:f058a822ffa8764dea58750d415d3a43f038fe79acd0f5dc473704393cc5d65d"
52 | ],
53 | "index": "pypi",
54 | "version": "==1.1.0"
55 | },
56 | "django": {
57 | "hashes": [
58 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
59 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
60 | ],
61 | "index": "pypi",
62 | "version": "==3.1"
63 | },
64 | "django-allauth": {
65 | "hashes": [
66 | "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30"
67 | ],
68 | "index": "pypi",
69 | "version": "==0.42.0"
70 | },
71 | "djangorestframework": {
72 | "hashes": [
73 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
74 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
75 | ],
76 | "index": "pypi",
77 | "version": "==3.11.0"
78 | },
79 | "idna": {
80 | "hashes": [
81 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
82 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
83 | ],
84 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
85 | "version": "==2.10"
86 | },
87 | "oauthlib": {
88 | "hashes": [
89 | "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
90 | "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
91 | ],
92 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
93 | "version": "==3.1.0"
94 | },
95 | "python3-openid": {
96 | "hashes": [
97 | "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
98 | "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"
99 | ],
100 | "version": "==3.2.0"
101 | },
102 | "pytz": {
103 | "hashes": [
104 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
105 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
106 | ],
107 | "version": "==2020.1"
108 | },
109 | "requests": {
110 | "hashes": [
111 | "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
112 | "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
113 | ],
114 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
115 | "version": "==2.24.0"
116 | },
117 | "requests-oauthlib": {
118 | "hashes": [
119 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
120 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
121 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
122 | ],
123 | "version": "==1.3.0"
124 | },
125 | "sqlparse": {
126 | "hashes": [
127 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
128 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
129 | ],
130 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
131 | "version": "==0.3.1"
132 | },
133 | "urllib3": {
134 | "hashes": [
135 | "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
136 | "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
137 | ],
138 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
139 | "version": "==1.25.10"
140 | }
141 | },
142 | "develop": {}
143 | }
144 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch8-viewsets-and-routers/config/__init__.py
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/config/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for config project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1rc1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/dev/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '5kji9d4+u2=pw4k2bh+v4pt7(57)$hsns-g_ma0oskgf*aa16&'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'django.contrib.sites', # new
41 |
42 | # 3rd-party apps
43 | 'rest_framework',
44 | 'rest_framework.authtoken',
45 | 'allauth', # new
46 | 'allauth.account', # new
47 | 'allauth.socialaccount', # new
48 | 'dj_rest_auth',
49 | 'dj_rest_auth.registration', # new
50 |
51 | # Local
52 | 'posts',
53 | ]
54 |
55 | MIDDLEWARE = [
56 | 'django.middleware.security.SecurityMiddleware',
57 | 'django.contrib.sessions.middleware.SessionMiddleware',
58 | 'django.middleware.common.CommonMiddleware',
59 | 'django.middleware.csrf.CsrfViewMiddleware',
60 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
61 | 'django.contrib.messages.middleware.MessageMiddleware',
62 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
63 | ]
64 |
65 | ROOT_URLCONF = 'config.urls'
66 |
67 | TEMPLATES = [
68 | {
69 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
70 | 'DIRS': [],
71 | 'APP_DIRS': True,
72 | 'OPTIONS': {
73 | 'context_processors': [
74 | 'django.template.context_processors.debug',
75 | 'django.template.context_processors.request',
76 | 'django.contrib.auth.context_processors.auth',
77 | 'django.contrib.messages.context_processors.messages',
78 | ],
79 | },
80 | },
81 | ]
82 |
83 | WSGI_APPLICATION = 'config.wsgi.application'
84 |
85 |
86 | # Database
87 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
88 |
89 | DATABASES = {
90 | 'default': {
91 | 'ENGINE': 'django.db.backends.sqlite3',
92 | 'NAME': BASE_DIR / 'db.sqlite3',
93 | }
94 | }
95 |
96 |
97 | # Password validation
98 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
99 |
100 | AUTH_PASSWORD_VALIDATORS = [
101 | {
102 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
103 | },
104 | {
105 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
106 | },
107 | {
108 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
109 | },
110 | {
111 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
112 | },
113 | ]
114 |
115 |
116 | # Internationalization
117 | # https://docs.djangoproject.com/en/dev/topics/i18n/
118 |
119 | LANGUAGE_CODE = 'en-us'
120 |
121 | TIME_ZONE = 'UTC'
122 |
123 | USE_I18N = True
124 |
125 | USE_L10N = True
126 |
127 | USE_TZ = True
128 |
129 |
130 | # Static files (CSS, JavaScript, Images)
131 | # https://docs.djangoproject.com/en/dev/howto/static-files/
132 |
133 | STATIC_URL = '/static/'
134 |
135 | REST_FRAMEWORK = {
136 | 'DEFAULT_PERMISSION_CLASSES': [
137 | 'rest_framework.permissions.IsAuthenticated',
138 | ],
139 | 'DEFAULT_AUTHENTICATION_CLASSES': [
140 | 'rest_framework.authentication.SessionAuthentication',
141 | 'rest_framework.authentication.TokenAuthentication', # new
142 | ],
143 | }
144 |
145 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # new
146 |
147 | SITE_ID = 1 # new
148 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 |
5 | urlpatterns = [
6 | path('admin/', admin.site.urls),
7 | path('api/v1/', include('posts.urls')),
8 | path('api-auth/', include('rest_framework.urls')),
9 | path('api/v1/dj-rest-auth/', include('dj_rest_auth.urls')),
10 | path('api/v1/dj-rest-auth/registration/', # new
11 | include('dj_rest_auth.registration.urls')),
12 | ]
13 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch8-viewsets-and-routers/db.sqlite3
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/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', 'config.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 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch8-viewsets-and-routers/posts/__init__.py
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | admin.site.register(Post)
5 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-29 17:28
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=50)),
22 | ('body', models.TextField()),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch8-viewsets-and-routers/posts/migrations/__init__.py
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Post(models.Model):
6 | author = models.ForeignKey(User, on_delete=models.CASCADE)
7 | title = models.CharField(max_length=50)
8 | body = models.TextField()
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return self.title
14 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class IsAuthorOrReadOnly(permissions.BasePermission):
5 |
6 | def has_object_permission(self, request, view, obj):
7 | # Read-only permissions are allowed for any request
8 | if request.method in permissions.SAFE_METHODS:
9 | return True
10 |
11 | # Write permissions are only allowed to the author of a post
12 | return obj.author == request.user
13 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model # new
2 | from rest_framework import serializers
3 | from .models import Post
4 |
5 |
6 | class PostSerializer(serializers.ModelSerializer):
7 |
8 | class Meta:
9 | fields = ('id', 'author', 'title', 'body', 'created_at',)
10 | model = Post
11 |
12 |
13 | class UserSerializer(serializers.ModelSerializer): # new
14 |
15 | class Meta:
16 | model = get_user_model()
17 | fields = ('id', 'username',)
18 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth.models import User
3 | from .models import Post
4 |
5 |
6 | class BlogTests(TestCase):
7 |
8 | @classmethod
9 | def setUpTestData(cls):
10 | # Create a user
11 | testuser1 = User.objects.create_user(
12 | username='testuser1', password='abc123')
13 | testuser1.save()
14 |
15 | # Create a blog post
16 | test_post = Post.objects.create(
17 | author=testuser1, title='Blog title', body='Body content...')
18 | test_post.save()
19 |
20 | def test_blog_content(self):
21 | post = Post.objects.get(id=1)
22 | author = f'{post.author}'
23 | title = f'{post.title}'
24 | body = f'{post.body}'
25 | self.assertEqual(author, 'testuser1')
26 | self.assertEqual(title, 'Blog title')
27 | self.assertEqual(body, 'Body content...')
28 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from rest_framework.routers import SimpleRouter
3 | from .views import UserViewSet, PostViewSet
4 |
5 | router = SimpleRouter()
6 | router.register('users', UserViewSet, basename='users')
7 | router.register('', PostViewSet, basename='posts')
8 |
9 | urlpatterns = router.urls
10 |
--------------------------------------------------------------------------------
/ch8-viewsets-and-routers/posts/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import viewsets # new
3 | from .models import Post
4 | from .permissions import IsAuthorOrReadOnly
5 | from .serializers import PostSerializer, UserSerializer
6 |
7 |
8 | class PostViewSet(viewsets.ModelViewSet): # new
9 | permission_classes = (IsAuthorOrReadOnly,)
10 | queryset = Post.objects.all()
11 | serializer_class = PostSerializer
12 |
13 |
14 | class UserViewSet(viewsets.ModelViewSet): # new
15 | queryset = get_user_model().objects.all()
16 | serializer_class = UserSerializer
17 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "~=3.1.0"
10 | djangorestframework = "==3.11.0"
11 | dj-rest-auth = "==1.1.0"
12 | django-allauth = "~=0.42.0"
13 | pyyaml = "==5.3.1"
14 | uritemplate = "==3.0.1"
15 | drf-yasg = "==1.17.1"
16 |
17 | [requires]
18 | python_version = "3.8"
19 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "636607643e63d342b61e7df3cdcf6385cabc2f93d356c521b2f66b12e3a46649"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "asgiref": {
20 | "hashes": [
21 | "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
22 | "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
23 | ],
24 | "markers": "python_version >= '3.5'",
25 | "version": "==3.2.10"
26 | },
27 | "certifi": {
28 | "hashes": [
29 | "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
30 | "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
31 | ],
32 | "version": "==2020.6.20"
33 | },
34 | "chardet": {
35 | "hashes": [
36 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
37 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
38 | ],
39 | "version": "==3.0.4"
40 | },
41 | "coreapi": {
42 | "hashes": [
43 | "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb",
44 | "sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"
45 | ],
46 | "version": "==2.3.3"
47 | },
48 | "coreschema": {
49 | "hashes": [
50 | "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f",
51 | "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"
52 | ],
53 | "version": "==0.0.4"
54 | },
55 | "defusedxml": {
56 | "hashes": [
57 | "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
58 | "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
59 | ],
60 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
61 | "version": "==0.6.0"
62 | },
63 | "dj-rest-auth": {
64 | "hashes": [
65 | "sha256:f058a822ffa8764dea58750d415d3a43f038fe79acd0f5dc473704393cc5d65d"
66 | ],
67 | "index": "pypi",
68 | "version": "==1.1.0"
69 | },
70 | "django": {
71 | "hashes": [
72 | "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b",
73 | "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"
74 | ],
75 | "index": "pypi",
76 | "version": "==3.1"
77 | },
78 | "django-allauth": {
79 | "hashes": [
80 | "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30"
81 | ],
82 | "index": "pypi",
83 | "version": "==0.42.0"
84 | },
85 | "djangorestframework": {
86 | "hashes": [
87 | "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
88 | "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
89 | ],
90 | "index": "pypi",
91 | "version": "==3.11.0"
92 | },
93 | "drf-yasg": {
94 | "hashes": [
95 | "sha256:5572e9d5baab9f6b49318169df9789f7399d0e3c7bdac8fdb8dfccf1d5d2b1ca",
96 | "sha256:7d7af27ad16e18507e9392b2afd6b218fbffc432ec8dbea053099a2241e184ff"
97 | ],
98 | "index": "pypi",
99 | "version": "==1.17.1"
100 | },
101 | "idna": {
102 | "hashes": [
103 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
104 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
105 | ],
106 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
107 | "version": "==2.10"
108 | },
109 | "inflection": {
110 | "hashes": [
111 | "sha256:88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9",
112 | "sha256:f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924"
113 | ],
114 | "markers": "python_version >= '3.5'",
115 | "version": "==0.5.0"
116 | },
117 | "itypes": {
118 | "hashes": [
119 | "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6",
120 | "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"
121 | ],
122 | "version": "==1.2.0"
123 | },
124 | "jinja2": {
125 | "hashes": [
126 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
127 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
128 | ],
129 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
130 | "version": "==2.11.2"
131 | },
132 | "markupsafe": {
133 | "hashes": [
134 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
135 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
136 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
137 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
138 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
139 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
140 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
141 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
142 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
143 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
144 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
145 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
146 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
147 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
148 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
149 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
150 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
151 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
152 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
153 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
154 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
155 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
156 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
157 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
158 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
159 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
160 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
161 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
162 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
163 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
164 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
165 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
166 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
167 | ],
168 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
169 | "version": "==1.1.1"
170 | },
171 | "oauthlib": {
172 | "hashes": [
173 | "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
174 | "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
175 | ],
176 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
177 | "version": "==3.1.0"
178 | },
179 | "packaging": {
180 | "hashes": [
181 | "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
182 | "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
183 | ],
184 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
185 | "version": "==20.4"
186 | },
187 | "pyparsing": {
188 | "hashes": [
189 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
190 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
191 | ],
192 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
193 | "version": "==2.4.7"
194 | },
195 | "python3-openid": {
196 | "hashes": [
197 | "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
198 | "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"
199 | ],
200 | "version": "==3.2.0"
201 | },
202 | "pytz": {
203 | "hashes": [
204 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
205 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
206 | ],
207 | "version": "==2020.1"
208 | },
209 | "pyyaml": {
210 | "hashes": [
211 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
212 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
213 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
214 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
215 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
216 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
217 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
218 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
219 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
220 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
221 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
222 | ],
223 | "index": "pypi",
224 | "version": "==5.3.1"
225 | },
226 | "requests": {
227 | "hashes": [
228 | "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
229 | "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
230 | ],
231 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
232 | "version": "==2.24.0"
233 | },
234 | "requests-oauthlib": {
235 | "hashes": [
236 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
237 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
238 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
239 | ],
240 | "version": "==1.3.0"
241 | },
242 | "ruamel.yaml": {
243 | "hashes": [
244 | "sha256:0962fd7999e064c4865f96fb1e23079075f4a2a14849bcdc5cdba53a24f9759b",
245 | "sha256:099c644a778bf72ffa00524f78dd0b6476bca94a1da344130f4bf3381ce5b954"
246 | ],
247 | "version": "==0.16.10"
248 | },
249 | "ruamel.yaml.clib": {
250 | "hashes": [
251 | "sha256:1e77424825caba5553bbade750cec2277ef130647d685c2b38f68bc03453bac6",
252 | "sha256:392b7c371312abf27fb549ec2d5e0092f7ef6e6c9f767bfb13e83cb903aca0fd",
253 | "sha256:4d55386129291b96483edcb93b381470f7cd69f97585829b048a3d758d31210a",
254 | "sha256:550168c02d8de52ee58c3d8a8193d5a8a9491a5e7b2462d27ac5bf63717574c9",
255 | "sha256:57933a6986a3036257ad7bf283529e7c19c2810ff24c86f4a0cfeb49d2099919",
256 | "sha256:615b0396a7fad02d1f9a0dcf9f01202bf9caefee6265198f252c865f4227fcc6",
257 | "sha256:77556a7aa190be9a2bd83b7ee075d3df5f3c5016d395613671487e79b082d784",
258 | "sha256:7aee724e1ff424757b5bd8f6c5bbdb033a570b2b4683b17ace4dbe61a99a657b",
259 | "sha256:8073c8b92b06b572e4057b583c3d01674ceaf32167801fe545a087d7a1e8bf52",
260 | "sha256:9c6d040d0396c28d3eaaa6cb20152cb3b2f15adf35a0304f4f40a3cf9f1d2448",
261 | "sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5",
262 | "sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070",
263 | "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c",
264 | "sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30",
265 | "sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947",
266 | "sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc",
267 | "sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973",
268 | "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad",
269 | "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e"
270 | ],
271 | "markers": "platform_python_implementation == 'CPython' and python_version < '3.9'",
272 | "version": "==0.2.0"
273 | },
274 | "six": {
275 | "hashes": [
276 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
277 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
278 | ],
279 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
280 | "version": "==1.15.0"
281 | },
282 | "sqlparse": {
283 | "hashes": [
284 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
285 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
286 | ],
287 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
288 | "version": "==0.3.1"
289 | },
290 | "uritemplate": {
291 | "hashes": [
292 | "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
293 | "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
294 | ],
295 | "index": "pypi",
296 | "version": "==3.0.1"
297 | },
298 | "urllib3": {
299 | "hashes": [
300 | "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
301 | "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
302 | ],
303 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
304 | "version": "==1.25.10"
305 | }
306 | },
307 | "develop": {}
308 | }
309 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch9-schemas-and-documentation/config/__init__.py
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/config/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/config/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for config project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1rc1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/dev/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = '5kji9d4+u2=pw4k2bh+v4pt7(57)$hsns-g_ma0oskgf*aa16&'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'django.contrib.sites',
41 |
42 | # 3rd-party apps
43 | 'rest_framework',
44 | 'rest_framework.authtoken',
45 | 'allauth',
46 | 'allauth.account',
47 | 'allauth.socialaccount',
48 | 'dj_rest_auth',
49 | 'dj_rest_auth.registration',
50 | 'drf_yasg', # new
51 |
52 | # Local
53 | 'posts',
54 | ]
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 = 'config.urls'
67 |
68 | TEMPLATES = [
69 | {
70 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
71 | 'DIRS': [],
72 | 'APP_DIRS': True,
73 | 'OPTIONS': {
74 | 'context_processors': [
75 | 'django.template.context_processors.debug',
76 | 'django.template.context_processors.request',
77 | 'django.contrib.auth.context_processors.auth',
78 | 'django.contrib.messages.context_processors.messages',
79 | ],
80 | },
81 | },
82 | ]
83 |
84 | WSGI_APPLICATION = 'config.wsgi.application'
85 |
86 |
87 | # Database
88 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
89 |
90 | DATABASES = {
91 | 'default': {
92 | 'ENGINE': 'django.db.backends.sqlite3',
93 | 'NAME': BASE_DIR / 'db.sqlite3',
94 | }
95 | }
96 |
97 |
98 | # Password validation
99 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
100 |
101 | AUTH_PASSWORD_VALIDATORS = [
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
104 | },
105 | {
106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
107 | },
108 | {
109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
110 | },
111 | {
112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
113 | },
114 | ]
115 |
116 |
117 | # Internationalization
118 | # https://docs.djangoproject.com/en/dev/topics/i18n/
119 |
120 | LANGUAGE_CODE = 'en-us'
121 |
122 | TIME_ZONE = 'UTC'
123 |
124 | USE_I18N = True
125 |
126 | USE_L10N = True
127 |
128 | USE_TZ = True
129 |
130 |
131 | # Static files (CSS, JavaScript, Images)
132 | # https://docs.djangoproject.com/en/dev/howto/static-files/
133 |
134 | STATIC_URL = '/static/'
135 |
136 | REST_FRAMEWORK = {
137 | 'DEFAULT_PERMISSION_CLASSES': [
138 | 'rest_framework.permissions.IsAuthenticated',
139 | ],
140 | 'DEFAULT_AUTHENTICATION_CLASSES': [
141 | 'rest_framework.authentication.SessionAuthentication',
142 | 'rest_framework.authentication.TokenAuthentication', # new
143 | ],
144 | }
145 |
146 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # new
147 |
148 | SITE_ID = 1 # new
149 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 | from rest_framework import permissions # new
4 | from drf_yasg.views import get_schema_view # new
5 | from drf_yasg import openapi # new
6 |
7 | schema_view = get_schema_view( # new
8 | openapi.Info(
9 | title="Blog API",
10 | default_version="v1",
11 | description="A sample API for learning DRF",
12 | terms_of_service="https://www.google.com/policies/terms/",
13 | contact=openapi.Contact(email="hello@example.com"),
14 | license=openapi.License(name="BSD License"),
15 | ),
16 | public=True,
17 | permission_classes=(permissions.AllowAny,),
18 | )
19 |
20 | urlpatterns = [
21 | path('admin/', admin.site.urls),
22 | path('api/v1/', include('posts.urls')),
23 | path('api-auth/', include('rest_framework.urls')),
24 | path('api/v1/dj-rest-auth/', include('dj_rest_auth.urls')),
25 | path('api/v1/dj-rest-auth/registration/',
26 | include('dj_rest_auth.registration.urls')),
27 | path('swagger/', schema_view.with_ui( # new
28 | 'swagger', cache_timeout=0), name='schema-swagger-ui'),
29 | path('redoc/', schema_view.with_ui( # new
30 | 'redoc', cache_timeout=0), name='schema-redoc'),
31 | ]
32 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for config 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/dev/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', 'config.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch9-schemas-and-documentation/db.sqlite3
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/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', 'config.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 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/openapi-schema.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.2
2 | info:
3 | title: ''
4 | version: ''
5 | paths:
6 | /api/v1/users/:
7 | get:
8 | operationId: listUsers
9 | description: ''
10 | parameters: []
11 | responses:
12 | '200':
13 | content:
14 | application/json:
15 | schema:
16 | type: array
17 | items:
18 | properties:
19 | id:
20 | type: integer
21 | readOnly: true
22 | username:
23 | type: string
24 | description: Required. 150 characters or fewer. Letters, digits
25 | and @/./+/-/_ only.
26 | pattern: ^[\w.@+-]+\Z
27 | maxLength: 150
28 | required:
29 | - username
30 | description: ''
31 | post:
32 | operationId: createUser
33 | description: ''
34 | parameters: []
35 | requestBody:
36 | content:
37 | application/json:
38 | schema: &id001
39 | properties:
40 | username:
41 | type: string
42 | description: Required. 150 characters or fewer. Letters, digits
43 | and @/./+/-/_ only.
44 | pattern: ^[\w.@+-]+\Z
45 | maxLength: 150
46 | required:
47 | - username
48 | application/x-www-form-urlencoded:
49 | schema: *id001
50 | multipart/form-data:
51 | schema: *id001
52 | responses:
53 | '200':
54 | content:
55 | application/json:
56 | schema:
57 | properties:
58 | id:
59 | type: integer
60 | readOnly: true
61 | username:
62 | type: string
63 | description: Required. 150 characters or fewer. Letters, digits
64 | and @/./+/-/_ only.
65 | pattern: ^[\w.@+-]+\Z
66 | maxLength: 150
67 | required:
68 | - username
69 | description: ''
70 | /api/v1/users/{id}/:
71 | get:
72 | operationId: retrieveUser
73 | description: ''
74 | parameters:
75 | - name: id
76 | in: path
77 | required: true
78 | description: A unique integer value identifying this user.
79 | schema:
80 | type: string
81 | responses:
82 | '200':
83 | content:
84 | application/json:
85 | schema:
86 | properties:
87 | id:
88 | type: integer
89 | readOnly: true
90 | username:
91 | type: string
92 | description: Required. 150 characters or fewer. Letters, digits
93 | and @/./+/-/_ only.
94 | pattern: ^[\w.@+-]+\Z
95 | maxLength: 150
96 | required:
97 | - username
98 | description: ''
99 | put:
100 | operationId: updateUser
101 | description: ''
102 | parameters:
103 | - name: id
104 | in: path
105 | required: true
106 | description: A unique integer value identifying this user.
107 | schema:
108 | type: string
109 | requestBody:
110 | content:
111 | application/json:
112 | schema: &id002
113 | properties:
114 | username:
115 | type: string
116 | description: Required. 150 characters or fewer. Letters, digits
117 | and @/./+/-/_ only.
118 | pattern: ^[\w.@+-]+\Z
119 | maxLength: 150
120 | required:
121 | - username
122 | application/x-www-form-urlencoded:
123 | schema: *id002
124 | multipart/form-data:
125 | schema: *id002
126 | responses:
127 | '200':
128 | content:
129 | application/json:
130 | schema:
131 | properties:
132 | id:
133 | type: integer
134 | readOnly: true
135 | username:
136 | type: string
137 | description: Required. 150 characters or fewer. Letters, digits
138 | and @/./+/-/_ only.
139 | pattern: ^[\w.@+-]+\Z
140 | maxLength: 150
141 | required:
142 | - username
143 | description: ''
144 | patch:
145 | operationId: partial_updateUser
146 | description: ''
147 | parameters:
148 | - name: id
149 | in: path
150 | required: true
151 | description: A unique integer value identifying this user.
152 | schema:
153 | type: string
154 | requestBody:
155 | content:
156 | application/json:
157 | schema: &id003
158 | properties:
159 | username:
160 | type: string
161 | description: Required. 150 characters or fewer. Letters, digits
162 | and @/./+/-/_ only.
163 | pattern: ^[\w.@+-]+\Z
164 | maxLength: 150
165 | application/x-www-form-urlencoded:
166 | schema: *id003
167 | multipart/form-data:
168 | schema: *id003
169 | responses:
170 | '200':
171 | content:
172 | application/json:
173 | schema:
174 | properties:
175 | id:
176 | type: integer
177 | readOnly: true
178 | username:
179 | type: string
180 | description: Required. 150 characters or fewer. Letters, digits
181 | and @/./+/-/_ only.
182 | pattern: ^[\w.@+-]+\Z
183 | maxLength: 150
184 | required:
185 | - username
186 | description: ''
187 | delete:
188 | operationId: destroyUser
189 | description: ''
190 | parameters:
191 | - name: id
192 | in: path
193 | required: true
194 | description: A unique integer value identifying this user.
195 | schema:
196 | type: string
197 | responses:
198 | '204':
199 | description: ''
200 | /api/v1/:
201 | get:
202 | operationId: listPosts
203 | description: ''
204 | parameters: []
205 | responses:
206 | '200':
207 | content:
208 | application/json:
209 | schema:
210 | type: array
211 | items:
212 | properties:
213 | id:
214 | type: integer
215 | readOnly: true
216 | author:
217 | type: integer
218 | title:
219 | type: string
220 | maxLength: 50
221 | body:
222 | type: string
223 | created_at:
224 | type: string
225 | format: date-time
226 | readOnly: true
227 | required:
228 | - author
229 | - title
230 | - body
231 | description: ''
232 | post:
233 | operationId: createPost
234 | description: ''
235 | parameters: []
236 | requestBody:
237 | content:
238 | application/json:
239 | schema: &id004
240 | properties:
241 | author:
242 | type: integer
243 | title:
244 | type: string
245 | maxLength: 50
246 | body:
247 | type: string
248 | required:
249 | - author
250 | - title
251 | - body
252 | application/x-www-form-urlencoded:
253 | schema: *id004
254 | multipart/form-data:
255 | schema: *id004
256 | responses:
257 | '200':
258 | content:
259 | application/json:
260 | schema:
261 | properties:
262 | id:
263 | type: integer
264 | readOnly: true
265 | author:
266 | type: integer
267 | title:
268 | type: string
269 | maxLength: 50
270 | body:
271 | type: string
272 | created_at:
273 | type: string
274 | format: date-time
275 | readOnly: true
276 | required:
277 | - author
278 | - title
279 | - body
280 | description: ''
281 | /api/v1/{id}/:
282 | get:
283 | operationId: retrievePost
284 | description: ''
285 | parameters:
286 | - name: id
287 | in: path
288 | required: true
289 | description: A unique integer value identifying this post.
290 | schema:
291 | type: string
292 | responses:
293 | '200':
294 | content:
295 | application/json:
296 | schema:
297 | properties:
298 | id:
299 | type: integer
300 | readOnly: true
301 | author:
302 | type: integer
303 | title:
304 | type: string
305 | maxLength: 50
306 | body:
307 | type: string
308 | created_at:
309 | type: string
310 | format: date-time
311 | readOnly: true
312 | required:
313 | - author
314 | - title
315 | - body
316 | description: ''
317 | put:
318 | operationId: updatePost
319 | description: ''
320 | parameters:
321 | - name: id
322 | in: path
323 | required: true
324 | description: A unique integer value identifying this post.
325 | schema:
326 | type: string
327 | requestBody:
328 | content:
329 | application/json:
330 | schema: &id005
331 | properties:
332 | author:
333 | type: integer
334 | title:
335 | type: string
336 | maxLength: 50
337 | body:
338 | type: string
339 | required:
340 | - author
341 | - title
342 | - body
343 | application/x-www-form-urlencoded:
344 | schema: *id005
345 | multipart/form-data:
346 | schema: *id005
347 | responses:
348 | '200':
349 | content:
350 | application/json:
351 | schema:
352 | properties:
353 | id:
354 | type: integer
355 | readOnly: true
356 | author:
357 | type: integer
358 | title:
359 | type: string
360 | maxLength: 50
361 | body:
362 | type: string
363 | created_at:
364 | type: string
365 | format: date-time
366 | readOnly: true
367 | required:
368 | - author
369 | - title
370 | - body
371 | description: ''
372 | patch:
373 | operationId: partial_updatePost
374 | description: ''
375 | parameters:
376 | - name: id
377 | in: path
378 | required: true
379 | description: A unique integer value identifying this post.
380 | schema:
381 | type: string
382 | requestBody:
383 | content:
384 | application/json:
385 | schema: &id006
386 | properties:
387 | author:
388 | type: integer
389 | title:
390 | type: string
391 | maxLength: 50
392 | body:
393 | type: string
394 | application/x-www-form-urlencoded:
395 | schema: *id006
396 | multipart/form-data:
397 | schema: *id006
398 | responses:
399 | '200':
400 | content:
401 | application/json:
402 | schema:
403 | properties:
404 | id:
405 | type: integer
406 | readOnly: true
407 | author:
408 | type: integer
409 | title:
410 | type: string
411 | maxLength: 50
412 | body:
413 | type: string
414 | created_at:
415 | type: string
416 | format: date-time
417 | readOnly: true
418 | required:
419 | - author
420 | - title
421 | - body
422 | description: ''
423 | delete:
424 | operationId: destroyPost
425 | description: ''
426 | parameters:
427 | - name: id
428 | in: path
429 | required: true
430 | description: A unique integer value identifying this post.
431 | schema:
432 | type: string
433 | responses:
434 | '204':
435 | description: ''
436 | /api/v1/dj-rest-auth/logout/:
437 | get:
438 | operationId: listLogouts
439 | description: 'Calls Django logout method and delete the Token object
440 |
441 | assigned to the current User object.
442 |
443 |
444 | Accepts/Returns nothing.'
445 | parameters: []
446 | responses:
447 | '200':
448 | content:
449 | application/json:
450 | schema:
451 | type: array
452 | items: {}
453 | description: ''
454 | post:
455 | operationId: CreateLogout
456 | description: 'Calls Django logout method and delete the Token object
457 |
458 | assigned to the current User object.
459 |
460 |
461 | Accepts/Returns nothing.'
462 | parameters: []
463 | responses:
464 | '200':
465 | content:
466 | application/json:
467 | schema: {}
468 | description: ''
469 | /api/v1/dj-rest-auth/user/:
470 | get:
471 | operationId: RetrieveUserDetails
472 | description: 'Reads and updates UserModel fields
473 |
474 | Accepts GET, PUT, PATCH methods.
475 |
476 |
477 | Default accepted fields: username, first_name, last_name
478 |
479 | Default display fields: pk, username, email, first_name, last_name
480 |
481 | Read-only fields: pk, email
482 |
483 |
484 | Returns UserModel fields.'
485 | parameters: []
486 | responses:
487 | '200':
488 | content:
489 | application/json:
490 | schema:
491 | properties:
492 | pk:
493 | type: integer
494 | readOnly: true
495 | username:
496 | type: string
497 | description: Required. 150 characters or fewer. Letters, digits
498 | and @/./+/-/_ only.
499 | pattern: ^[\w.@+-]+\Z
500 | maxLength: 150
501 | email:
502 | type: string
503 | format: email
504 | readOnly: true
505 | first_name:
506 | type: string
507 | maxLength: 30
508 | last_name:
509 | type: string
510 | maxLength: 150
511 | required:
512 | - username
513 | description: ''
514 | put:
515 | operationId: UpdateUserDetails
516 | description: 'Reads and updates UserModel fields
517 |
518 | Accepts GET, PUT, PATCH methods.
519 |
520 |
521 | Default accepted fields: username, first_name, last_name
522 |
523 | Default display fields: pk, username, email, first_name, last_name
524 |
525 | Read-only fields: pk, email
526 |
527 |
528 | Returns UserModel fields.'
529 | parameters: []
530 | requestBody:
531 | content:
532 | application/json:
533 | schema: &id007
534 | properties:
535 | username:
536 | type: string
537 | description: Required. 150 characters or fewer. Letters, digits
538 | and @/./+/-/_ only.
539 | pattern: ^[\w.@+-]+\Z
540 | maxLength: 150
541 | first_name:
542 | type: string
543 | maxLength: 30
544 | last_name:
545 | type: string
546 | maxLength: 150
547 | required:
548 | - username
549 | application/x-www-form-urlencoded:
550 | schema: *id007
551 | multipart/form-data:
552 | schema: *id007
553 | responses:
554 | '200':
555 | content:
556 | application/json:
557 | schema:
558 | properties:
559 | pk:
560 | type: integer
561 | readOnly: true
562 | username:
563 | type: string
564 | description: Required. 150 characters or fewer. Letters, digits
565 | and @/./+/-/_ only.
566 | pattern: ^[\w.@+-]+\Z
567 | maxLength: 150
568 | email:
569 | type: string
570 | format: email
571 | readOnly: true
572 | first_name:
573 | type: string
574 | maxLength: 30
575 | last_name:
576 | type: string
577 | maxLength: 150
578 | required:
579 | - username
580 | description: ''
581 | patch:
582 | operationId: PartialUpdateUserDetails
583 | description: 'Reads and updates UserModel fields
584 |
585 | Accepts GET, PUT, PATCH methods.
586 |
587 |
588 | Default accepted fields: username, first_name, last_name
589 |
590 | Default display fields: pk, username, email, first_name, last_name
591 |
592 | Read-only fields: pk, email
593 |
594 |
595 | Returns UserModel fields.'
596 | parameters: []
597 | requestBody:
598 | content:
599 | application/json:
600 | schema: &id008
601 | properties:
602 | username:
603 | type: string
604 | description: Required. 150 characters or fewer. Letters, digits
605 | and @/./+/-/_ only.
606 | pattern: ^[\w.@+-]+\Z
607 | maxLength: 150
608 | first_name:
609 | type: string
610 | maxLength: 30
611 | last_name:
612 | type: string
613 | maxLength: 150
614 | application/x-www-form-urlencoded:
615 | schema: *id008
616 | multipart/form-data:
617 | schema: *id008
618 | responses:
619 | '200':
620 | content:
621 | application/json:
622 | schema:
623 | properties:
624 | pk:
625 | type: integer
626 | readOnly: true
627 | username:
628 | type: string
629 | description: Required. 150 characters or fewer. Letters, digits
630 | and @/./+/-/_ only.
631 | pattern: ^[\w.@+-]+\Z
632 | maxLength: 150
633 | email:
634 | type: string
635 | format: email
636 | readOnly: true
637 | first_name:
638 | type: string
639 | maxLength: 30
640 | last_name:
641 | type: string
642 | maxLength: 150
643 | required:
644 | - username
645 | description: ''
646 | /api/v1/dj-rest-auth/password/reset/:
647 | post:
648 | operationId: CreatePasswordReset
649 | description: 'Calls Django Auth PasswordResetForm save method.
650 |
651 |
652 | Accepts the following POST parameters: email
653 |
654 | Returns the success/fail message.'
655 | parameters: []
656 | requestBody:
657 | content:
658 | application/json:
659 | schema: &id009
660 | properties:
661 | email:
662 | type: string
663 | format: email
664 | required:
665 | - email
666 | application/x-www-form-urlencoded:
667 | schema: *id009
668 | multipart/form-data:
669 | schema: *id009
670 | responses:
671 | '200':
672 | content:
673 | application/json:
674 | schema:
675 | properties:
676 | email:
677 | type: string
678 | format: email
679 | required:
680 | - email
681 | description: ''
682 | /api/v1/dj-rest-auth/password/reset/confirm/:
683 | post:
684 | operationId: CreatePasswordResetConfirm
685 | description: "Password reset e-mail link is confirmed, therefore\nthis resets\
686 | \ the user's password.\n\nAccepts the following POST parameters: token, uid,\n\
687 | \ new_password1, new_password2\nReturns the success/fail message."
688 | parameters: []
689 | requestBody:
690 | content:
691 | application/json:
692 | schema: &id010
693 | properties:
694 | new_password1:
695 | type: string
696 | maxLength: 128
697 | new_password2:
698 | type: string
699 | maxLength: 128
700 | uid:
701 | type: string
702 | token:
703 | type: string
704 | required:
705 | - new_password1
706 | - new_password2
707 | - uid
708 | - token
709 | application/x-www-form-urlencoded:
710 | schema: *id010
711 | multipart/form-data:
712 | schema: *id010
713 | responses:
714 | '200':
715 | content:
716 | application/json:
717 | schema:
718 | properties:
719 | new_password1:
720 | type: string
721 | maxLength: 128
722 | new_password2:
723 | type: string
724 | maxLength: 128
725 | uid:
726 | type: string
727 | token:
728 | type: string
729 | required:
730 | - new_password1
731 | - new_password2
732 | - uid
733 | - token
734 | description: ''
735 | /api/v1/dj-rest-auth/login/:
736 | post:
737 | operationId: CreateLogin
738 | description: 'Check the credentials and return the REST Token
739 |
740 | if the credentials are valid and authenticated.
741 |
742 | Calls Django Auth login method to register User ID
743 |
744 | in Django session framework
745 |
746 |
747 | Accept the following POST parameters: username, password
748 |
749 | Return the REST Framework Token Object''s key.'
750 | parameters: []
751 | requestBody:
752 | content:
753 | application/json:
754 | schema: &id011
755 | properties:
756 | username:
757 | type: string
758 | email:
759 | type: string
760 | format: email
761 | password:
762 | type: string
763 | required:
764 | - password
765 | application/x-www-form-urlencoded:
766 | schema: *id011
767 | multipart/form-data:
768 | schema: *id011
769 | responses:
770 | '200':
771 | content:
772 | application/json:
773 | schema:
774 | properties:
775 | username:
776 | type: string
777 | email:
778 | type: string
779 | format: email
780 | password:
781 | type: string
782 | required:
783 | - password
784 | description: ''
785 | /api/v1/dj-rest-auth/password/change/:
786 | post:
787 | operationId: CreatePasswordChange
788 | description: 'Calls Django Auth SetPasswordForm save method.
789 |
790 |
791 | Accepts the following POST parameters: new_password1, new_password2
792 |
793 | Returns the success/fail message.'
794 | parameters: []
795 | requestBody:
796 | content:
797 | application/json:
798 | schema: &id012
799 | properties:
800 | new_password1:
801 | type: string
802 | maxLength: 128
803 | new_password2:
804 | type: string
805 | maxLength: 128
806 | required:
807 | - new_password1
808 | - new_password2
809 | application/x-www-form-urlencoded:
810 | schema: *id012
811 | multipart/form-data:
812 | schema: *id012
813 | responses:
814 | '200':
815 | content:
816 | application/json:
817 | schema:
818 | properties:
819 | new_password1:
820 | type: string
821 | maxLength: 128
822 | new_password2:
823 | type: string
824 | maxLength: 128
825 | required:
826 | - new_password1
827 | - new_password2
828 | description: ''
829 | /api/v1/dj-rest-auth/registration/:
830 | post:
831 | operationId: CreateRegister
832 | description: ''
833 | parameters: []
834 | requestBody:
835 | content:
836 | application/json:
837 | schema: &id013
838 | properties:
839 | username:
840 | type: string
841 | maxLength: 150
842 | minLength: 1
843 | email:
844 | type: string
845 | format: email
846 | password1:
847 | type: string
848 | writeOnly: true
849 | password2:
850 | type: string
851 | writeOnly: true
852 | required:
853 | - username
854 | - password1
855 | - password2
856 | application/x-www-form-urlencoded:
857 | schema: *id013
858 | multipart/form-data:
859 | schema: *id013
860 | responses:
861 | '200':
862 | content:
863 | application/json:
864 | schema:
865 | properties:
866 | username:
867 | type: string
868 | maxLength: 150
869 | minLength: 1
870 | email:
871 | type: string
872 | format: email
873 | required:
874 | - username
875 | description: ''
876 | /api/v1/dj-rest-auth/registration/verify-email/:
877 | post:
878 | operationId: CreateVerifyEmail
879 | description: ''
880 | parameters: []
881 | requestBody:
882 | content:
883 | application/json:
884 | schema: &id014
885 | properties:
886 | key:
887 | type: string
888 | required:
889 | - key
890 | application/x-www-form-urlencoded:
891 | schema: *id014
892 | multipart/form-data:
893 | schema: *id014
894 | responses:
895 | '200':
896 | content:
897 | application/json:
898 | schema:
899 | properties:
900 | key:
901 | type: string
902 | required:
903 | - key
904 | description: ''
905 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch9-schemas-and-documentation/posts/__init__.py
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | admin.site.register(Post)
5 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1rc1 on 2020-07-29 17:28
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=50)),
22 | ('body', models.TextField()),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/ch9-schemas-and-documentation/posts/migrations/__init__.py
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Post(models.Model):
6 | author = models.ForeignKey(User, on_delete=models.CASCADE)
7 | title = models.CharField(max_length=50)
8 | body = models.TextField()
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return self.title
14 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class IsAuthorOrReadOnly(permissions.BasePermission):
5 |
6 | def has_object_permission(self, request, view, obj):
7 | # Read-only permissions are allowed for any request
8 | if request.method in permissions.SAFE_METHODS:
9 | return True
10 |
11 | # Write permissions are only allowed to the author of a post
12 | return obj.author == request.user
13 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model # new
2 | from rest_framework import serializers
3 | from .models import Post
4 |
5 |
6 | class PostSerializer(serializers.ModelSerializer):
7 |
8 | class Meta:
9 | fields = ('id', 'author', 'title', 'body', 'created_at',)
10 | model = Post
11 |
12 |
13 | class UserSerializer(serializers.ModelSerializer): # new
14 |
15 | class Meta:
16 | model = get_user_model()
17 | fields = ('id', 'username',)
18 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth.models import User
3 | from .models import Post
4 |
5 |
6 | class BlogTests(TestCase):
7 |
8 | @classmethod
9 | def setUpTestData(cls):
10 | # Create a user
11 | testuser1 = User.objects.create_user(
12 | username='testuser1', password='abc123')
13 | testuser1.save()
14 |
15 | # Create a blog post
16 | test_post = Post.objects.create(
17 | author=testuser1, title='Blog title', body='Body content...')
18 | test_post.save()
19 |
20 | def test_blog_content(self):
21 | post = Post.objects.get(id=1)
22 | author = f'{post.author}'
23 | title = f'{post.title}'
24 | body = f'{post.body}'
25 | self.assertEqual(author, 'testuser1')
26 | self.assertEqual(title, 'Blog title')
27 | self.assertEqual(body, 'Body content...')
28 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from rest_framework.routers import SimpleRouter
3 | from .views import UserViewSet, PostViewSet
4 |
5 | router = SimpleRouter()
6 | router.register('users', UserViewSet, basename='users')
7 | router.register('', PostViewSet, basename='posts')
8 |
9 | urlpatterns = router.urls
10 |
--------------------------------------------------------------------------------
/ch9-schemas-and-documentation/posts/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import viewsets # new
3 | from .models import Post
4 | from .permissions import IsAuthorOrReadOnly
5 | from .serializers import PostSerializer, UserSerializer
6 |
7 |
8 | class PostViewSet(viewsets.ModelViewSet): # new
9 | permission_classes = (IsAuthorOrReadOnly,)
10 | queryset = Post.objects.all()
11 | serializer_class = PostSerializer
12 |
13 |
14 | class UserViewSet(viewsets.ModelViewSet): # new
15 | queryset = get_user_model().objects.all()
16 | serializer_class = UserSerializer
17 |
--------------------------------------------------------------------------------
/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangoforapis_31/c678537076d809f4fea19f0beb5e20e3be8661e2/cover.jpg
--------------------------------------------------------------------------------