From 2cee6a41429a091a253d6eb592cee760eab0b437 Mon Sep 17 00:00:00 2001 From: bobby-customate Date: Tue, 8 Nov 2022 11:38:12 +0000 Subject: [PATCH 01/16] Module_1 complete --- backend/drf_course/__init__.py | 0 backend/drf_course/asgi.py | 16 +++++ backend/drf_course/settings.py | 123 +++++++++++++++++++++++++++++++++ backend/drf_course/urls.py | 21 ++++++ backend/drf_course/wsgi.py | 16 +++++ backend/manage.py | 22 ++++++ 6 files changed, 198 insertions(+) create mode 100644 backend/drf_course/__init__.py create mode 100644 backend/drf_course/asgi.py create mode 100644 backend/drf_course/settings.py create mode 100644 backend/drf_course/urls.py create mode 100644 backend/drf_course/wsgi.py create mode 100644 backend/manage.py diff --git a/backend/drf_course/__init__.py b/backend/drf_course/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/drf_course/asgi.py b/backend/drf_course/asgi.py new file mode 100644 index 00000000..0bb087d3 --- /dev/null +++ b/backend/drf_course/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for drf_course project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_course.settings') + +application = get_asgi_application() diff --git a/backend/drf_course/settings.py b/backend/drf_course/settings.py new file mode 100644 index 00000000..59120bfe --- /dev/null +++ b/backend/drf_course/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for drf_course project. + +Generated by 'django-admin startproject' using Django 4.1.3. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-02jc+u2q_sr%f41&t2)0lm^(&qqptt2!6+dj!k#)4)obmc@+lt' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'drf_course.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'drf_course.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/backend/drf_course/urls.py b/backend/drf_course/urls.py new file mode 100644 index 00000000..416e4f1e --- /dev/null +++ b/backend/drf_course/urls.py @@ -0,0 +1,21 @@ +"""drf_course URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/backend/drf_course/wsgi.py b/backend/drf_course/wsgi.py new file mode 100644 index 00000000..66fab8d1 --- /dev/null +++ b/backend/drf_course/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for drf_course project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_course.settings') + +application = get_wsgi_application() diff --git a/backend/manage.py b/backend/manage.py new file mode 100644 index 00000000..4e3ceab4 --- /dev/null +++ b/backend/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_course.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() From 0a64e19dd3689b111005eb20cf9560435751efe1 Mon Sep 17 00:00:00 2001 From: bobby-customate Date: Tue, 8 Nov 2022 11:40:51 +0000 Subject: [PATCH 02/16] Module_2 complete --- backend/core/__init__.py | 0 backend/core/admin.py | 3 ++ backend/core/apps.py | 6 ++++ backend/core/migrations/__init__.py | 0 backend/core/models.py | 3 ++ backend/core/tests.py | 3 ++ backend/core/views.py | 3 ++ backend/drf_course/settings.py | 45 +++++++++++++++++++++-------- backend/drf_course/urls.py | 24 +++++---------- 9 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 backend/core/__init__.py create mode 100644 backend/core/admin.py create mode 100644 backend/core/apps.py create mode 100644 backend/core/migrations/__init__.py create mode 100644 backend/core/models.py create mode 100644 backend/core/tests.py create mode 100644 backend/core/views.py diff --git a/backend/core/__init__.py b/backend/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/core/admin.py b/backend/core/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/backend/core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/core/apps.py b/backend/core/apps.py new file mode 100644 index 00000000..8115ae60 --- /dev/null +++ b/backend/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/backend/core/migrations/__init__.py b/backend/core/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/core/models.py b/backend/core/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/backend/core/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/backend/core/tests.py b/backend/core/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/backend/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/core/views.py b/backend/core/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/backend/core/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/backend/drf_course/settings.py b/backend/drf_course/settings.py index 59120bfe..035affaa 100644 --- a/backend/drf_course/settings.py +++ b/backend/drf_course/settings.py @@ -11,21 +11,15 @@ """ from pathlib import Path +from dotenv import load_dotenv +import os +load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-02jc+u2q_sr%f41&t2)0lm^(&qqptt2!6+dj!k#)4)obmc@+lt' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] +SECRET_KEY = os.environ.get("SECRET_KEY") +DEBUG = int(os.environ.get("DEBUG", default=0)) +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") # Application definition @@ -37,6 +31,10 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_extensions', #Great packaged to access abstract models + 'django_filters', #Used with DRF + 'rest_framework', #DRF package + 'core', # New app ] MIDDLEWARE = [ @@ -121,3 +119,26 @@ # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +REST_FRAMEWORK = { + 'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler', + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework_json_api.parsers.JSONParser', + ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework_json_api.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer' + ), + 'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata', + 'DEFAULT_FILTER_BACKENDS': ( + 'rest_framework_json_api.filters.QueryParameterValidationFilter', + 'rest_framework_json_api.filters.OrderingFilter', + 'rest_framework_json_api.django_filters.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + ), + 'SEARCH_PARAM': 'filter[search]', + 'TEST_REQUEST_RENDERER_CLASSES': ( + 'rest_framework_json_api.renderers.JSONRenderer', + ), + 'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json' +} diff --git a/backend/drf_course/urls.py b/backend/drf_course/urls.py index 416e4f1e..af9ea8bd 100644 --- a/backend/drf_course/urls.py +++ b/backend/drf_course/urls.py @@ -1,21 +1,11 @@ -"""drf_course URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.1/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -from django.contrib import admin from django.urls import path +from django.contrib import admin +from rest_framework import routers + +router = routers.DefaultRouter() + +urlpatterns = router.urls -urlpatterns = [ +urlpatterns += [ path('admin/', admin.site.urls), ] From 5dde1b74f3b6515b328f13d0e6da59a0de168b60 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 8 Nov 2022 13:24:57 +0000 Subject: [PATCH 03/16] Update module_3.md --- steps/module_3.md | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/steps/module_3.md b/steps/module_3.md index e10eb93a..6224e02f 100644 --- a/steps/module_3.md +++ b/steps/module_3.md @@ -194,7 +194,7 @@ urlpatterns += [ ] ``` -6) Register - Go ahead and open /core/admin.py and paste in the follwing code to register the new models to the built in admin page. +6) Register - Go ahead and open /core/admin.py and paste in the following code to register the new models to the built in admin page. ``` from django.contrib import admin @@ -213,8 +213,43 @@ python manage.py makemigrations python manage.py migrate ``` -You will now be able to see the fruits of your labour at [http://localhost:8000](http://localhost:8000). ->Note: We will fully test the endpoint in the next video +8) Call our endpoints - Here are the requests we can make to our new endpoint. + +>Note: change 'localhost' to 'api' if you make the calls via Docker Decktop. + +> This will create a contact request + +curl -X POST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://localhost:8000/contact/' + +http http://api:8000/contact/ name="Bobby Stearman" message="test" email="bobby@didcoding.com" + +if it went well, you should see something like the following in your terminal. + +``` +HTTP/1.1 200 OK +Allow: POST, OPTIONS +Content-Length: 157 +Content-Type: application/vnd.api+json +Cross-Origin-Opener-Policy: same-origin +Date: Tue, 08 Nov 2022 13:22:57 GMT +Referrer-Policy: same-origin +Server: WSGIServer/0.2 CPython/3.10.8 +Vary: Accept +X-Content-Type-Options: nosniff +X-Frame-Options: DENY + +{ + "data": { + "attributes": { + "email": "bobby@didcoding.com", + "message": "test", + "name": "Bobby Stearman" + }, + "id": "b37b5fa7-7cdd-4594-961b-8489ad66fd83", + "type": "Contact" + } +} +``` *** @@ -265,4 +300,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From 7da602337678b1874b74725b14583d3a6789380c Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 8 Nov 2022 13:31:21 +0000 Subject: [PATCH 04/16] Update module_5.md --- steps/module_5.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/steps/module_5.md b/steps/module_5.md index cbce2355..23c5be0e 100644 --- a/steps/module_5.md +++ b/steps/module_5.md @@ -125,6 +125,7 @@ from rest_framework import routers from rest_framework.authtoken.views import obtain_auth_token router = routers.DefaultRouter() +router.register(r'contact', core_views.ContactViewSet, basename='contact') urlpatterns = router.urls @@ -168,7 +169,7 @@ class EcommerceConfig(AppConfig): ``` -5) Create a user - Go ahead and create a new superuser. This will server 2 purposes. We we gain access to the built in Django admin page and we will also create a new token. Open a new terminal and use the follwing code. +5) Create a user - Go ahead and create a new superuser. This will server 2 purposes. We we gain access to the built in Django admin page and we will also create a new token. Open a new terminal and use the following code. ``` python manage.py createsuperuser ``` @@ -178,22 +179,24 @@ Add a username, email and password. The following commands will call the API end point: +>Note: change 'localhost' to 'api' if you make the calls via Docker Decktop. + ``` -curl -XPOST -F 'username=bobby@didcoding.com' -F 'password=fredfred1' http://localhost:8000/api-token-auth/ +curl -XPOST -F 'username=**your_username**' -F 'password=**your_password**' http://localhost:8000/api-token-auth/ ``` ``` -http post http://localhost:8000/api-token-auth/ username=bobby@didcoding.com password=fredfred1 +http post http://localhost:8000/api-token-auth/ username=**your_username** password=**your_password** ``` These can both be used in the Docker CLI ``` -curl -XPOST -F 'username=bobby@didcoding.com' -F 'password=fredfred1' http://api:8000/api-token-auth/ +curl -XPOST -F 'username=**your_username**' -F 'password=**your_password**' http://api:8000/api-token-auth/ ``` ``` -http post http://api:8000/api-token-auth/ username=bobby@didcoding.com password=fredfred1 +http post http://api:8000/api-token-auth/ username=**your_username** password=**your_password** ``` With any luck, you should see something that looks like the following: @@ -272,4 +275,4 @@ drf_course\ <--This is the root directory >server.py ``` *** -*** \ No newline at end of file +*** From c409ff3fde68f92325d0c3709ce8e9d0dd9474b8 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 8 Nov 2022 13:36:41 +0000 Subject: [PATCH 05/16] Update module_6.md --- steps/module_6.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/steps/module_6.md b/steps/module_6.md index 8adb284e..97f1fea2 100644 --- a/steps/module_6.md +++ b/steps/module_6.md @@ -171,7 +171,6 @@ class Order( from collections import OrderedDict from .models import Item , Order -from .exceptions import NotEnoughStockException from rest_framework_json_api import serializers from rest_framework import status from rest_framework.exceptions import APIException @@ -187,7 +186,7 @@ class NotEnoughStockException(APIException): -class ItemSerializer(serializers.HyperlinkedModelSerializer): +class ItemSerializer(serializers.ModelSerializer): class Meta: model = Item @@ -200,7 +199,7 @@ class ItemSerializer(serializers.HyperlinkedModelSerializer): -class OrderSerializer(serializers.HyperlinkedModelSerializer): +class OrderSerializer(serializers.ModelSerializer): item = serializers.PrimaryKeyRelatedField(queryset = Item.objects.all(), many=False) @@ -304,4 +303,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From aeeb9e5db7eff50e285e357c72a7d07e76b9856e Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:40:51 +0000 Subject: [PATCH 06/16] Update module_1.md --- steps/module_1.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/steps/module_1.md b/steps/module_1.md index c6fa5696..f7ce2e23 100644 --- a/steps/module_1.md +++ b/steps/module_1.md @@ -12,7 +12,6 @@ drf_course\ <--This is the root directory backend\ docker\ ... - static\ >requirements.txt steps\ ... @@ -104,7 +103,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ >manage.py >requirements.txt steps\ @@ -122,4 +120,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From c2cac927fc71d1eb3e9e758d377c51d39b9a24f0 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:46:58 +0000 Subject: [PATCH 07/16] Update module_2.md --- steps/module_2.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/steps/module_2.md b/steps/module_2.md index 71ca8d81..3ab68090 100644 --- a/steps/module_2.md +++ b/steps/module_2.md @@ -18,7 +18,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ >manage.py >requirements.txt steps\ @@ -75,7 +74,7 @@ DEBUG = int(os.environ.get("DEBUG", default=0)) ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") ``` -installed apps: Django will only know to include our new apps in the project when we register them. Open drf_course/settings.py and register the new application in INSTALLED_APPS. Replace the current settings with the following snippet. +installed apps: Django will only know to include our new app in the project when we register it. Open drf_course/settings.py and register the new application in INSTALLED_APPS. Replace the current settings with the following snippet. ``` INSTALLED_APPS = [ 'django.contrib.admin', @@ -216,7 +215,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ >manage.py >requirements.txt steps\ @@ -234,4 +232,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From 8a93a6ef3c40d2a9748733ccb44ccddca458dfa4 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:00:29 +0000 Subject: [PATCH 08/16] Update module_3.md --- steps/module_3.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/steps/module_3.md b/steps/module_3.md index 6224e02f..3674c7ec 100644 --- a/steps/module_3.md +++ b/steps/module_3.md @@ -28,7 +28,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ >manage.py >requirements.txt steps\ @@ -137,7 +136,7 @@ class ContactSerializer(serializers.ModelSerializer): ``` -4) ViewSets - A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create(). +4) APIView - Using the APIView class is pretty much the same as using a regular View class, as usual, the incoming request is dispatched to an appropriate handler method such as .get() or .post(). Additionally, a number of attributes may be set on the class that control various aspects of the API policy. Go ahead and paste the following code into /backend/core/views.py @@ -146,18 +145,29 @@ from json import JSONDecodeError from django.http import JsonResponse from .serializers import ContactSerializer from rest_framework.parsers import JSONParser -from rest_framework import viewsets, status +from rest_framework import views, status from rest_framework.response import Response -class ContactViewSet(viewsets.GenericViewSet): +class ContactAPIView(views.APIView): """ - A simple ViewSet for creating contact entires. + A simple APIView for creating contact entires. """ serializer_class = ContactSerializer - def create(self, request): + def get_serializer_context(self): + return { + 'request': self.request, + 'format': self.format_kwarg, + 'view': self + } + + def get_serializer(self, *args, **kwargs): + kwargs['context'] = self.get_serializer_context() + return self.serializer_class(*args, **kwargs) + + def post(self, request): try: data = JSONParser().parse(request) serializer = ContactSerializer(data=data) @@ -185,12 +195,12 @@ from rest_framework import routers router = routers.DefaultRouter() -router.register(r'contact', core_views.ContactViewSet, basename='contact') #new endpoint for '/contact/' urlpatterns = router.urls urlpatterns += [ path('admin/', admin.site.urls), + path('contact/', core_views.ContactAPIView.as_view()), ] ``` @@ -219,7 +229,7 @@ python manage.py migrate > This will create a contact request -curl -X POST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://localhost:8000/contact/' +curl -X POST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://api:8000/contact/' http http://api:8000/contact/ name="Bobby Stearman" message="test" email="bobby@didcoding.com" @@ -279,7 +289,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ utils\ <--New directory >__init__.py >model_abstracts.py From c6c1e30aefb357dd8e29d0c62f65f26e07c593f4 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:07:33 +0000 Subject: [PATCH 09/16] Update module_4.md --- steps/module_4.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/steps/module_4.md b/steps/module_4.md index 5f6e3329..b5c768d2 100644 --- a/steps/module_4.md +++ b/steps/module_4.md @@ -29,7 +29,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ utils\ >__init__.py >model_abstracts.py @@ -57,11 +56,9 @@ git pull origin module_4 ## Steps/Commands >Note: Please 'cd' into the root directory and fire up your virtual environment! -In the last module, we built a '/contact/' end point for uses to get in touch with us. It seems to work okay but let's double down on testing. +In the last module, we built a '/contact/' end point for users to get in touch with us. It seems to work okay but let's double down on testing. In this module, we will write some unit tests to test our new endpoint. -DRF comes with a built in APIClient to - 1) Unit tests - Copy the following code into /core/tests.py ``` from . models import Contact @@ -167,19 +164,12 @@ python manage.py test 3) Call API - We're ready to call the 'contact' endpoint. I like to user (Curl)[https://curl.se/] or (Httpie)[https://httpie.io/]. -You'll need to fire up Docker if you've decdied to go down the docker route. If this is the case, use the following command. ``` -docker-compose up -d --build +curl -XPOST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://api:8000/contact/' ``` -Now open a cli in the 'drf_course_api' container...You're now ready to us the following commands. ->Note: Change 'localhost' to 'api' if you are using docker ``` -curl -XPOST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://localhost:8000/contact/' -``` - -``` -http post http://localhost:8000/contact/ name='Bobby Stearman' message='This is a test' email=bobby@didcoding.com +http post http://api:8000/contact/ name='Bobby Stearman' message='This is a test' email=bobby@didcoding.com ``` @@ -190,7 +180,7 @@ python manage.py shell Now use the following command and check the database for our new entry ``` from core.models import Contact -c = Contact.objects.latest(created) +c = Contact.objects.last() c.title ``` @@ -222,7 +212,6 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ utils\ >__init__.py >model_abstracts.py @@ -243,4 +232,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From c7a2a77e2d4d00510471c5d3972bcaec8a9c43a5 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:15:14 +0000 Subject: [PATCH 10/16] Update module_5.md --- steps/module_5.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/steps/module_5.md b/steps/module_5.md index 23c5be0e..7ee7748c 100644 --- a/steps/module_5.md +++ b/steps/module_5.md @@ -29,10 +29,10 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -125,12 +125,12 @@ from rest_framework import routers from rest_framework.authtoken.views import obtain_auth_token router = routers.DefaultRouter() -router.register(r'contact', core_views.ContactViewSet, basename='contact') urlpatterns = router.urls urlpatterns += [ path('admin/', admin.site.urls), + path('contact/', core_views.ContactAPIView.as_view()), path('api-token-auth/', obtain_auth_token), #gives us access to token auth ] ``` @@ -179,18 +179,6 @@ Add a username, email and password. The following commands will call the API end point: ->Note: change 'localhost' to 'api' if you make the calls via Docker Decktop. - - -``` -curl -XPOST -F 'username=**your_username**' -F 'password=**your_password**' http://localhost:8000/api-token-auth/ -``` - -``` -http post http://localhost:8000/api-token-auth/ username=**your_username** password=**your_password** -``` - -These can both be used in the Docker CLI ``` curl -XPOST -F 'username=**your_username**' -F 'password=**your_password**' http://api:8000/api-token-auth/ ``` @@ -198,6 +186,7 @@ curl -XPOST -F 'username=**your_username**' -F 'password=**your_password**' http ``` http post http://api:8000/api-token-auth/ username=**your_username** password=**your_password** ``` + With any luck, you should see something that looks like the following: ``` @@ -255,10 +244,10 @@ drf_course\ <--This is the root directory >signals.py <--New file >tests.py >views.py - static\ utils\ >__init__.py - >model_abstracts.py + >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ From ef6dc5fd5534bff76e60c1500491c159ed992e21 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:19:07 +0000 Subject: [PATCH 11/16] Update module_6.md --- steps/module_6.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/steps/module_6.md b/steps/module_6.md index 97f1fea2..5172df06 100644 --- a/steps/module_6.md +++ b/steps/module_6.md @@ -40,10 +40,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -72,9 +72,9 @@ git pull origin module_6 Lets expand on what we have already learned and create a new app and few new endpoints. This time we will use enforce token authentication. This means only authenticated users can make calls to the endpoints. -We will build an e-commerce app with an item and order endpoint. Users will be able to retreive items in the database, place and order and retreive order information. +We will build an e-commerce app with an item and order endpoint. Users will be able to retrieve items in the database, place and order and retrieve order information. -We need models, routers, serializers and viewsets! Lets not waist any time... +We need models, routers, serializers and view-sets/api-view! Let's not waist any time... 1) Model - Go ahead and open /ecommerce/models.py and paste in the following code. ``` @@ -282,10 +282,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ From a8e626e4fe870083c0050d81276edaaf1dab057f Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:22:59 +0000 Subject: [PATCH 12/16] Update module_7.md --- steps/module_7.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/steps/module_7.md b/steps/module_7.md index 9f6c1b46..4afbcf34 100644 --- a/steps/module_7.md +++ b/steps/module_7.md @@ -41,10 +41,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -151,7 +151,6 @@ from rest_framework.authtoken.views import obtain_auth_token router = routers.DefaultRouter() -router.register(r'contact', core_views.ContactViewSet, basename='contact') router.register(r'item', ecommerce_views.ItemViewSet, basename='item') router.register(r'order', ecommerce_views.OrderViewSet, basename='order') @@ -159,6 +158,7 @@ urlpatterns = router.urls urlpatterns += [ path('admin/', admin.site.urls), + path('contact/', core_views.ContactAPIView.as_view()), path('api-token-auth/', obtain_auth_token), ] ``` @@ -202,10 +202,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -223,4 +223,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From 497b21f83ab6861eb61332e9724a3e8df8f0c6f5 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:27:16 +0000 Subject: [PATCH 13/16] Update module_8.md --- steps/module_8.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/steps/module_8.md b/steps/module_8.md index 118501cf..d54bd255 100644 --- a/steps/module_8.md +++ b/steps/module_8.md @@ -41,10 +41,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -225,52 +225,50 @@ Let's make some calls to our new endpoint. 3) Call our endpoints - Here are the requests we can make to our new endpoints. ->Note: change 'localhost' to 'api' if you make the calls via Docker Decktop. - > This retrieves the auth token for **your_username** -curl -X POST -F 'username=**your_username**' -F 'password=**your_password**' http://localhost:8000/api-token-auth/ +curl -X POST -F 'username=**your_username**' -F 'password=**your_password**' http://api:8000/api-token-auth/ -http post http://localhost:8000/api-token-auth/ username=**your_username** password=**mypassword** +http post http://api:8000/api-token-auth/ username=**your_username** password=**your_password** > This will retrieve all items -curl -X GET -H 'Authorization: Token **your_token**' http://localhost:8000/item/ +curl -X GET -H 'Authorization: Token **your_token**' http://api:8000/item/ -http http://localhost:8000/item/ 'Authorization: Token **your_token**' +http http://api:8000/item/ 'Authorization: Token **your_token**' > This will retreive a single item -curl -X GET -H 'Authorization: Token **your_token**' http://localhost:8000/item/**your_item_uuid**/ +curl -X GET -H 'Authorization: Token **your_token**' http://api:8000/item/**your_item_uuid**/ -http http://localhost:8000/item/**your_item_uuid**/ 'Authorization: Token **your_token**' +http http://api:8000/item/**your_item_uuid**/ 'Authorization: Token **your_token**' > This retrieve all orders -curl -X GET -H 'Authorization: Token **your_token**' http://localhost:8000/order/ +curl -X GET -H 'Authorization: Token **your_token**' http://api:8000/order/ -http http://localhost:8000/order/ 'Authorization: Token **your_token**' +http http://api:8000/order/ 'Authorization: Token **your_token**' > This will place an order for item id = **your_item_uuid** quantity = 1 -curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Token **your_token**' -d '{"item": "**your_item_uuid**", "quantity": "1"}' http://localhost:8000/order/ +curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Token **your_token**' -d '{"item": "**your_item_uuid**", "quantity": "1"}' http://api:8000/order/ http http://api:8000/order/ 'Authorization: Token **your_token**' item="**your_item_uuid**" quantity="1" > This get order id = **your_order_uuid** -curl -X GET -H 'Authorization: Token **your_token**' http://localhost:8000/order/**your_order_uuid**/ +curl -X GET -H 'Authorization: Token **your_token**' http://api:8000/order/**your_order_uuid**/ -http http://localhost:8000/order/**your_order_uuid**/ 'Authorization: Token **your_token**' +http http://api:8000/order/**your_order_uuid**/ 'Authorization: Token **your_token**' > This will create a contact request -curl -X POST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://localhost:8000/contact/' +curl -X POST -H "Content-type: application/json" -d '{"name": "Bobby Stearman", "message": "test", "email":"bobby@didcoding.com"}' 'http://api:8000/contact/' -http http://localhost:8000/contact/ name="Bobby Stearman" message="test" email="bobby@didcoding.com" +http http://api:8000/contact/ name="Bobby Stearman" message="test" email="bobby@didcoding.com" Congratulations!! You have a fully functioning and tested API!! @@ -313,10 +311,10 @@ drf_course\ <--This is the root directory >signals.py >tests.py >views.py - static\ utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -334,4 +332,4 @@ drf_course\ <--This is the root directory ``` *** -*** \ No newline at end of file +*** From a031eb2ad1706e8ed20f8daf92f98c229fe3c137 Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:29:35 +0000 Subject: [PATCH 14/16] Update module_2.md --- steps/module_2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/steps/module_2.md b/steps/module_2.md index 3ab68090..d2df7126 100644 --- a/steps/module_2.md +++ b/steps/module_2.md @@ -215,6 +215,7 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py + >db.sqlite3 <--New file >manage.py >requirements.txt steps\ From c01cc8c7d682b8942e28ceab668cfdfe6d7c0c7b Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:31:56 +0000 Subject: [PATCH 15/16] Update module_3.md --- steps/module_3.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/steps/module_3.md b/steps/module_3.md index 3674c7ec..7e995758 100644 --- a/steps/module_3.md +++ b/steps/module_3.md @@ -28,6 +28,7 @@ drf_course\ <--This is the root directory >settings.py >urls.py >wsgi.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -292,6 +293,7 @@ drf_course\ <--This is the root directory utils\ <--New directory >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ From 1dfba54f1272c1f92fa2949c1038b6845b34ac7e Mon Sep 17 00:00:00 2001 From: Bobby Stearman <80459294+bobby-didcoding@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:33:52 +0000 Subject: [PATCH 16/16] Update module_4.md --- steps/module_4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/steps/module_4.md b/steps/module_4.md index b5c768d2..4fda8c6b 100644 --- a/steps/module_4.md +++ b/steps/module_4.md @@ -32,6 +32,7 @@ drf_course\ <--This is the root directory utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\ @@ -215,6 +216,7 @@ drf_course\ <--This is the root directory utils\ >__init__.py >model_abstracts.py + >db.sqlite3 >manage.py >requirements.txt steps\