Related
I am using Django Rest Framework and OpenStack (DevStack deployed on a Virtual Machine on my local PC) to create APIs which will run methods provided by OpenStack SDK and return the response in JSON format.
However, while fetching(GET Request) a list of Servers created on OpenStack Cloud Platform, the Response for the first time after starting the Django Server is proper and desired, but after first GET Request, all GET requests sent are returning an empty List as the Response.
It must be noted that I have not changed anything in the code or in the Endpoint(URL), the same scenario keeps on repeating when I restart the Django Server, desired GET Response on first Request and Empty List for all the GET Requests onward.
I do not have any models of my own in models.py.
First GET Request Response:-
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"name": "test_shell",
"image": "openstack.image.v2.image.Image(id=b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0, properties={'links': [{'rel': 'bookmark', 'href': 'http://192.168.56.101/compute/images/b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0'}]})",
"flavor": "openstack.compute.v2.flavor.Flavor(vcpus=1, ram=128, disk=1, OS-FLV-EXT-DATA:ephemeral=0, swap=0, original_name=m1.nano, extra_specs={'hw_rng:allowed': 'True'})",
"networks": null,
"status": "ACTIVE",
"power_state": "1"
},
{
"name": "ins_code_4",
"image": "openstack.image.v2.image.Image(id=b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0, properties={'links': [{'rel': 'bookmark', 'href': 'http://192.168.56.101/compute/images/b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0'}]})",
"flavor": "openstack.compute.v2.flavor.Flavor(vcpus=1, ram=128, disk=1, OS-FLV-EXT-DATA:ephemeral=0, swap=0, original_name=m1.nano, extra_specs={'hw_rng:allowed': 'True'})",
"networks": null,
"status": "SHUTOFF",
"power_state": "4"
},
{
"name": "ins_code_3",
"image": "openstack.image.v2.image.Image(id=b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0, properties={'links': [{'rel': 'bookmark', 'href': 'http://192.168.56.101/compute/images/b6019f25-6f6d-4fd2-9fb8-14d50a07d2c0'}]})",
"flavor": "openstack.compute.v2.flavor.Flavor(vcpus=1, ram=128, disk=1, OS-FLV-EXT-DATA:ephemeral=0, swap=0, original_name=m1.nano, extra_specs={'hw_rng:allowed': 'True'})",
"networks": null,
"status": "SHUTOFF",
"power_state": "4"
}
]
GET Response after first Request:-
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[]
Django Code:-
Root settings.py
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.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-#gd5-6=)lwq4&bo(gywzy7ftic+5r!hgs$(%1gyqbt=pbwk=*a'
# 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',
'rest_framework',
'api',
]
# Custom by me
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
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 = 'cloudhome.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ BASE_DIR / "templates" ],
'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 = 'cloudhome.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.0/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.0/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.0/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static"
]
STATIC_ROOT = "static_root"
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Root urls.py:-
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api/', include('api.urls', namespace="api")),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
App "api" urls.py:-
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from api import views
app_name = "api"
urlpatterns = [
path('servers/', views.ServerList.as_view()),
path('servers/<slug:pk>/', views.ServerDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
App "api" views.py:-
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from requests import request
from rest_framework.parsers import JSONParser
from api.serializers import ServerSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import generics
import openstack
from openstack import connection
from rest_framework import authentication, permissions
from rest_framework.permissions import IsAuthenticated
openstack.enable_logging(debug=True)
conn=connection.Connection(auth_url='http://192.168.56.101/identity/v3',
project_name='admin',username='admin',
password='nomoresecret',
user_domain_id='default',
project_domain_id='default', verify=False)
class ServerList(generics.ListAPIView):
queryset = conn.compute.servers()
serializer_class = ServerSerializer
permission_classes = [permissions.IsAuthenticated]
class ServerDetail(generics.RetrieveAPIView):
queryset = conn.compute.servers()
serializer_class = ServerSerializer
permission_classes = [permissions.IsAuthenticated]
App "api" serializers.py:-
import openstack
from openstack import connection
from requests import Response
from rest_framework import serializers
from rest_framework import status
openstack.enable_logging(debug=True)
conn=connection.Connection(auth_url='http://192.168.56.101/identity/v3',
project_name='admin',username='admin',
password='nomoresecret',
user_domain_id='default',
project_domain_id='default', verify=False)
class ServerSerializer(serializers.Serializer):
instance_id = serializers.CharField(read_only=True)
name = serializers.CharField(max_length=100)
image = serializers.CharField(max_length=500)
fixed_ip = serializers.IPAddressField(read_only=True)
floating_ip = serializers.IPAddressField(required=False, allow_blank=True)
flavor = serializers.CharField(max_length=500)
networks = serializers.CharField(max_length=500)
key_pair = serializers.CharField(max_length=100, required=False, allow_blank=True)
status = serializers.CharField(max_length=20, read_only=True)
power_state = serializers.CharField(max_length=20, read_only=True)
console_url = serializers.URLField(max_length=100, read_only=True)
def create(self, validated_data):
serverCreated = conn.compute.create_server(name=validated_data["instance_name"], image_id=validated_data["image_id"],
flavor_id=validated_data["flavor_id"], networks=[{"uuid": validated_data["network_id"]}])
serverWait = conn.compute.wait_for_server(serverCreated)
print(serverWait.access_ipv4)
return serverWait
Kindly help me to resolve this issue and feel free to suggest changes to make this code more efficient. Thanks in advance.
I guess, it is something about django-pwa. This problem appears only in google chrome (in local mode it even works fine on 127.0.0.1:8000 but doesnt work on localhost:8000).
My project structure:
D:.
├───api
│ └───migrations
├───core
│ └───__pycache__
├───games
│ └───migrations
├───main
│ ├───migrations
│ └───__pycache__
├───static
│ ├───images
│ └───js
├───staticfiles
└───templates
it appears, that chrome browser requests the empty path for pwa.urls firstly, but other browsers requesting main urls. i dont really how to make chrome request my urls from main app on first place.
this is my urls.py:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.views.generic import TemplateView
from rest_framework.schemas import get_schema_view
import debug_toolbar
schema_url_patterns = [
path('api/v1/', include('api.urls')),
]
urlpatterns = [
path(r'', include('main.urls')),
path('openapi', get_schema_view(
title="Gamers Gazette",
description="API!",
version="1.0.0",
patterns=schema_url_patterns,
), name='openapi-schema'),
path('swagger-ui/', TemplateView.as_view(
template_name='swagger-ui.html',
extra_context={'schema_url':'openapi-schema'}
), name='swagger-ui'),
path('redoc/', TemplateView.as_view(
template_name='redoc.html',
extra_context={'schema_url':'openapi-schema'}
), name='redoc'),
path('admin/', admin.site.urls),
path('games/', include('games.urls')),
path('api/v1/', include('api.urls')),
path("", include("pwa.urls")),
]
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path('__debug__/', include('debug_toolbar.urls')),
] + urlpatterns
this is my serviceworker.js:
var staticCacheName = 'djangopwa-v1';
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('offline');
})
)
});
the settings.py:
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'static/js', 'serviceworker.js')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-fl61624yl*s1hbc9lt#9ipbmyytdrurqo#dc%$3pdz9g8haof3'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
SESSIONS_ENGINE='django.contrib.sessions.backends.cache'
ALLOWED_HOSTS = []
INSTALLED_APPS = [
#apps
'main',
'api',
'games',
#django staff
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#drf staff
'rest_framework',
#debugging
"debug_toolbar",
#third_party
"pwa"
]
MIDDLEWARE = [
#django staff
"debug_toolbar.middleware.DebugToolbarMiddleware",
'debug_toolbar_force.middleware.ForceDebugToolbarMiddleware',
'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 = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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 = 'core.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'main_db',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'db',
'PORT': '5432'
}
}
# Password validation
# https://docs.djangoproject.com/en/4.0/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',
},
]
if DEBUG:
import socket # only if you haven't already imported this
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[:-1] + '1' for ip in ips] + ['127.0.0.1', '10.0.2.2']
# Internationalization
# https://docs.djangoproject.com/en/4.0/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.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static/"),
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': 'cache:11211',
}
}
#PWA STAFF
PWA_APP_NAME = 'test app'
PWA_APP_DESCRIPTION = "test app PWA"
PWA_APP_THEME_COLOR = '#000000'
PWA_APP_BACKGROUND_COLOR = '#ffffff'
PWA_APP_DISPLAY = 'standalone'
PWA_APP_SCOPE = '/'
PWA_APP_ORIENTATION = 'any'
PWA_APP_START_URL = '/'
PWA_APP_STATUS_BAR_COLOR = 'default'
PWA_APP_ICONS = [
{
'src': 'static/images/icon.png',
'sizes': '160x160'
}
]
PWA_APP_ICONS_APPLE = [
{
'src': 'static/images/icon.png',
'sizes': '160x160'
}
]
PWA_APP_SPLASH_SCREEN = [
{
'src': 'static/images/icon.png',
'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
}
]
PWA_APP_DIR = 'ltr'
PWA_APP_LANG = 'ru-RU'
import django_heroku
django_heroku.settings(locals())
ask eweryting You need
Sorry for iterrupting, the problew was in Google. It cached a wrong version of script, after cleaning cache ewerything was fine.
I created a django project tha uses a customUser for authentication, following this blog post.
I now would like to attach another app to the project that is a REST API that leverages that authentication.
In this new app I create a model for the data that the uses will consume and I add as foreign key in the model the custom user model previously created.
#project/api/models.py
from django.contrib.postgres.fields import ArrayField
from django.db import models
from accounts.models import CustomUser
class Signal(models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
data = ArrayField(models.FloatField(), unique=True)
threshold = models.FloatField(blank=True, null=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
def __str__(self) -> str:
return f"{self.name}"
while the project/api/views.py file is
from django.template.defaultfilters import pluralize
from rest_framework.decorators import api_view
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from . import serializers
from .algorithm.compute_crossing import count_crossing_pos as count_cross
from .models import Signal
class SignalViewset(ListCreateAPIView):
permission_classes = [IsAuthenticated]
queryset = Signal.objects.all()
serializer_class = serializers.SignalSerializer
def get_queryset(self):
return super().get_queryset().filter(user=self.request.user)
class SignalDetail(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated]
queryset = Signal.objects.all()
serializer_class = serializers.SignalSerializer
def get_queryset(self):
return super().get_queryset().filter(user=self.request.user)
#api_view(http_method_names=["POST"])
def send_data(request: Request) -> Response():
"""send signal data with threshold in request payload
and compute crossing times around given threshold
:param request: [request payload]
:type request: [type]
"""
if request.user.is_authenticated:
count = count_cross(request.data.get("data"), request.data.get("threshold"))
return Response(
{
"status": "success",
"info": "signal crosses the given threshold {} tim{}".format(
count, pluralize(count, "e,es")
),
"count": str(count),
},
status=200,
)
else:
count = count_cross(request.data.get("data"), request.data.get("threshold"))
return Response(
{
"status": "success",
"info": "you are an AnonymousUser but I will give you an answer nonetheless \n \n signal crosses the given threshold {} tim{}".format(
count, pluralize(count, "e,es")
),
"count": str(count),
},
status=200,
)
Everytime I access the send_data endpoint I go into the else statement as request.user.is_authenticated is False.
how do I connect the rest api with the authentication (accounts) app?
my settings.py file is:
from pathlib import Path
from decouple import config
# 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/3.2/howto/deployment/checklist/
SYSTEM_ENV = config("SYSTEM_ENV")
ALLOWED_HOSTS = [
"*",
]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"api",
"rest_framework",
"rest_framework.authtoken",
"dj_rest_auth",
"django.contrib.sites",
"allauth",
"allauth.account",
"dj_rest_auth.registration",
"accounts",
]
AUTH_USER_MODEL = "accounts.CustomUser" # new
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 = "signal_crossing.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [str(BASE_DIR.joinpath("templates"))],
"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 = "signal_crossing.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
if SYSTEM_ENV == "PRODUCTION" or SYSTEM_ENV == "STAGING":
print("docker")
DEBUG = False
SECRET_KEY = config("SECRET_KEY")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "db",
"PORT": 5432,
}
}
elif SYSTEM_ENV == "GITHUB_WORKFLOW":
DEBUG = True
SECRET_KEY = "TESTING_KEY"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "github_actions",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
elif SYSTEM_ENV == "DEVELOPMENT":
DEBUG = True
SECRET_KEY = "DEVELOP_KEY"
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.postgresql_psycopg2",
# "NAME": config("DB_NAME"),
# "USER": config("POSTGRES_LOCAL_USER"),
# "PASSWORD": config("POSTGRES_LOCAL_PASSWORD"),
# "HOST": "localhost",
# "PORT": "5432",
# }
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/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/3.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = "/static/"
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
SITE_ID = 1
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# 'rest_framework.authentication.TokenAuthentication',
"dj_rest_auth.jwt_auth.JWTCookieAuthentication",
],
}
REST_USE_JWT = True
JWT_AUTH_COOKIE = "my-app-auth"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# allauth specific authentication methods, such as login by e-mail
"allauth.account.auth_backends.AuthenticationBackend",
# Needed to login by username in Django admin, regardless of allauth
"django.contrib.auth.backends.ModelBackend",
]
# ACCOUNT_AUTHENTICATION_METHOD = "username"
# LOGIN_URL = "/login/"
# LOGIN_REDIRECT_URL = "/"
# LOGOUT_REDIRECT_URL = "/"
DJANGO_SETTINGS_MODULE = "signal_crossing.settings"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"
I've built a Django API that uses django-graphql-auth and django-graphql-jwt packages to implement authentication. I followed the package's documentation and got everything to work and everything is working from my Angular UI. The only issue is that even requests made from Postman without the Authorization header, are able to fetch the data from the graphql API.
This is my Django project's settings.py
"""
Django settings for myproject project.
Generated by 'django-admin startproject' using Django 3.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from pathlib import Path
import os
import sys
# 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/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-)3#2sm6lgn_p83_t(l-44hd16ou5-qbk=rso!$b1#$fu*n2^rq'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
CORS_ORIGIN_ALLOW_ALL = True
# Application definition
INSTALLED_APPS = [
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
'graphene_django',
'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
'graphql_auth',
'rest_framework',
'django_filters'
]
GRAPHENE = {
'SCHEMA': 'myproject.schema.schema',
'MIDDLEWARE': [
'graphql_jwt.middleware.JSONWebTokenMiddleware',
],
}
GRAPHENE_DJANGO_EXTRAS = {
'DEFAULT_PAGINATION_CLASS': 'graphene_django_extras.paginations.LimitOffsetGraphqlPagination',
'DEFAULT_PAGE_SIZE': 20,
'MAX_PAGE_SIZE': 50,
'CACHE_ACTIVE': True,
'CACHE_TIMEOUT': 300 # seconds
}
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.utils.UpdateLastActivityMiddleware'
]
AUTHENTICATION_BACKENDS = [
'graphql_auth.backends.GraphQLAuthBackend',
'django.contrib.auth.backends.ModelBackend',
]
GRAPHQL_JWT = {
"JWT_ALLOW_ANY_CLASSES": [
"graphql_auth.mutations.Register",
"graphql_auth.mutations.VerifyAccount",
"graphql_auth.mutations.ResendActivationEmail",
"graphql_auth.mutations.SendPasswordResetEmail",
"graphql_auth.mutations.PasswordReset",
"graphql_auth.mutations.ObtainJSONWebToken",
"graphql_auth.mutations.VerifyToken",
"graphql_auth.mutations.RefreshToken",
"graphql_auth.mutations.RevokeToken",
],
'JWT_PAYLOAD_HANDLER': 'common.utils.jwt_payload',
"JWT_VERIFY_EXPIRATION": True,
"JWT_LONG_RUNNING_REFRESH_TOKEN": True
}
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'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 = 'myproject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myprojectdb',
'USER': 'myprojectadmin',
'PASSWORD': 'password',
'HOST': 'db',
'PORT': '5432',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/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/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = (
BASE_DIR / "static",
'/var/www/static/',
)
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# This is here because we are using a custom User model
# https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = "myapp.User"
urls.py
from django.contrib import admin
from django.urls import include, path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', include('myapp.urls')),
path('admin/', admin.site.urls),
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Sample query:-
query users {
users {
id
nickName
lastLogin
}
}
As you can see, I've followed all the necessary steps to accomodate the instructions in both the packages. How do I prevent unauthorized requests accessing my data?
Updates:-
I'm using Django-graphql-extras for pagination, filtering on my Graphql api. So the queries are all using the built-in methods from that package.
The mutations however are manual.
Queries file:-
from graphene_django.types import ObjectType
from .gqTypes import InstitutionType, UserType, GroupType
from graphene_django_extras import DjangoObjectField, DjangoFilterPaginateListField, LimitOffsetGraphqlPagination
class Query(ObjectType):
institution = DjangoObjectField(
InstitutionType, description='Single User query')
user = DjangoObjectField(UserType, description='Single User query')
group = DjangoObjectField(GroupType, description='Single User query')
institutions = DjangoFilterPaginateListField(
InstitutionType, pagination=LimitOffsetGraphqlPagination())
users = DjangoFilterPaginateListField(
UserType, pagination=LimitOffsetGraphqlPagination())
groups = DjangoFilterPaginateListField(
GroupType, pagination=LimitOffsetGraphqlPagination())
Sample mutation code:-
class CreateUser(graphene.Mutation):
class Meta:
description = "Mutation to create a new User"
class Arguments:
input = UserInput(required=True)
ok = graphene.Boolean()
user = graphene.Field(UserType)
#staticmethod
def mutate(root, info, input=None):
ok = True
error = ""
if input.name is None:
error += "Name is a required field<br />"
if len(error) > 0:
raise GraphQLError(error)
searchField = input.name
searchField += input.title if input.title is not None else ""
searchField += input.bio if input.bio is not None else ""
searchField = searchField.lower()
user_instance = User(user_id=input.user_id, title=input.title, bio=input.bio,
institution_id=input.institution_id, searchField=searchField)
user_instance.save()
return CreateUser(ok=ok, user=user_instance)
You should add the login_required decorator to your queries and mutations resolvers. Like this:
from graphql_jwt.decorators import login_required
class Query(graphene.ObjectType):
viewer = graphene.Field(UserType)
#login_required
def resolve_viewer(self, info, **kwargs):
return info.context.user
In your case put it after staticmethod decorator, like this:
#staticmethod
#login_required
def mutate():
pass
I am trying to have React upload an image via React DropZone. However, when I go to my blog manager page, the console promptly logs 404 Failed to load resource: the server responded with a status of 404 (Not Found). I am using Django Rest Framework for the backend, and in the terminal it states "GET /media/Upload%20image HTTP/1.1". The frontend, react, is able to get the image file on the page, but it will not upload.
Here is the settings.py in backend DRF:
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'p%s-9w1l268#!b$p#92dj26q)pv7!&ln^3m(1j5#!k8pkc9#(u'
# 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',
'django.contrib.sites',
# 'allauth',
# 'allauth.account',
# 'allauth.socialaccount',
'corsheaders',
# 'rest_auth',
# 'rest_auth.registration',
'rest_framework',
# 'rest_framework.authtoken',
'menus',
'phlogfeeder',
'login'
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'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 = 'chefsBackEnd.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 = 'chefsBackEnd.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/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'},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
Here is where my static file settings are:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'build/static'),
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# STATICFILES_FINDERS = [
# 'django.contrib.staticfiles.finders.FileSystemFinder',
# 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# ]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
# SITE_ID = 1
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.TokenAuthentication',
# 'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000'
]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'chefsBackEnd.utils.resp_handler',
# 'JWT_ENCODE_HANDLER':
# 'rest_framework_jwt.utils.jwt_encode_handler',
# 'JWT_DECODE_HANDLER':
# 'rest_framework_jwt.utils.jwt_decode_handler',
# 'JWT_PAYLOAD_HANDLER':
# 'rest_framework_jwt.utils.jwt_payload_handler',
# 'JWT_PAYLOAD_GET_USER_ID_HANDLER':
# 'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',
'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY': True,
}
My urls.py file:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from rest_framework_jwt.views import obtain_jwt_token
from rest_framework_jwt.views import verify_jwt_token
urlpatterns = [
path('api-auth/', include('rest_framework.urls')),
path('rest-auth/', include('rest_auth.urls')),
# path('rest-auth/registration/', include('rest_auth.registration.urls')),
path('token-auth/', obtain_jwt_token),
path('api-token-verify/', verify_jwt_token),
path('login/', include('login.urls')),
path('admin/', admin.site.urls),
path('api/', include('menus.api.urls')),
path('phlogapi/', include('phlogfeeder.phlogapi.urls'))
]
here I add the url patterns to provide access to static and media files
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
The console keeps saying upload/%20/media.
The front end:
phlogEditor.js
import React, { Component } from 'react';
import axios from 'axios';
import DropzoneComponent from 'react-dropzone-component';
import "../../../node_modules/react-dropzone-component/styles/filepicker.css";
import "../../../node_modules/dropzone/dist/min/dropzone.min.css";
class PhlogEditor extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
phlog_status: '',
phlog_image: '',
editMode: false,
position: '',
apiUrl: 'http://127.0.0.1:8000/phlogapi/phlog/create/',
apiAction: 'post'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.componentConfig = this.componentConfig.bind(this);
this.djsConfig = this.djsConfig.bind(this);
this.handlePhlogImageDrop = this.handlePhlogImageDrop.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.phlogImageRef = React.createRef();
}
deleteImage(event) {
event.preventDefault();
axios
.delete(
`http://127.0.0.1:8000/phlogapi/phlog/${this.props.id}/delete`,
{ withCredentials: true }
)
.then(response => {
this.props.handlePhlogImageDelete();
})
.catch(error => {
console.log('deleteImage failed', error)
});
}
componentDidUpdate() {
if (Object.keys(this.props.phlogToEdit).length > 0) {
const {
id,
phlog_image,
phlog_status,
position
} = this.props.phlogToEdit;
this.props.clearPhlogsToEdit();
this.setState({
id: id,
phlog_image: phlog_image || '',
phlog_status: phlog_status || '',
position: position || '',
editMode: true,
apiUrl: `http://127.0.0.1:8000/phlogapi/phlog/${this.props.id}/update`,
apiAction: 'patch'
});
}
}
handlePhlogImageDrop() {
return {
addedfile: file => this.setState({ phlog_image: file })
};
}
componentConfig() {
return {
iconFiletypes: [".jpg", ".png"],
showFiletypeIcon: true,
postUrl: "https://httpbin.org/post"
};
}
djsConfig() {
return {
addRemoveLinks: true,
maxFiles: 3
};
}
buildForm() {
let formData = new FormData();
formData.append('phlog[phlog_status]', this.state.phlog_status);
formData.append('phlog[position]', this.state.position);
if (this.state.phlog_image) {
formData.append(
'phlog[phlog_image]',
this.state.phlog_image
);
}
return formData;
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
axios({
method: this.state.apiAction,
url: this.state.apiUrl,
data: this.buildForm(),
withCredentials: true
})
.then(response => {
if (this.state.editMode) {
this.props.handlePhlogSubmission();
} else {
this.props.handleNewPhlogSubmission(response.data);
}
this.setState({
phlog_status: '',
phlog_image: '',
position: '',
editMode: false,
apiUrl:'http://127.0.0.1:8000/phlogapi/phlog/create/',
apiAction: 'post'
});
[this.phlogImageRef].forEach(ref => {
ref.current.dropzone.removeAllFiles();
});
})
.catch(error => {
console.log('handleSubmit phlogEditor error', error);
});
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} className='phlog-editor-wrapper'>
<div className='two-column'>
<input
type='text'
name='position'
placeholder='Position'
value={this.state.position}
onChange={this.handleChange}
/>
<select
name='phlog status'
value={this.state.phlog_status}
onChange={this.handleChange}
className='select-element'
>
<option value='Draft'>Draft</option>
<option value='Published'>Published</option>
</select>
</div>
<div className='image-uploaders'>
{this.state.editMode && this.state.phlog_image_url ? (
<div className='phlog-manager-image-wrapper'>
<img src={this.state.phlog_image_url} />
<div className='remove-image-link'>
<a onClick={() => this.deleteImage('phlog_image')}>
Remove Photos
</a>
</div>
</div>
) : (
<DropzoneComponent
ref={this.phlogImageRef}
config={this.componentConfig()}
djsConfig={this.djsConfig()}
eventHandlers={this.handlePhlogImageDrop()}
>
<div className='phlog-msg'>Phlog Photo</div>
</DropzoneComponent>
)}
</div>
<button className='btn' type='submit'>Save</button>
</form>
);
}
}
export default PhlogEditor;
Add the Build folder to the INSTALLED_APPS in the settings.py file.