How to connect VUE 3.x frontend websocket to DJANGO 4.x consumer using DJANGOCHANNELSRESTFRAMEWORK? - django-rest-framework

I have a DJANGO project with the following files.
Basically I am using token authentication in my settings which works well for javascript fetch/axios since I can send the token in the headers. I use djoser to manage the token authentication; however when I try to run any custom action in my consumer.py file the user in self.scope.get('user') is always AnonymousUser and then it returns with an 'Not allowed' message.
Do I need to authenticate the user over websocket protocol?
Also worth noting. I am using DJANGOCHANNELSRESTFRAMEWORK and using DEMULTIPLEXER to handle the websocket/consumer routing.
UPDATE: I tried using using the 'list' action in my json data and in consumers.py I user permissions.AllowAny and it works as expected; however I still cannot use any of my own custom actions.
UPDATE 2: When using permissions.AllowAny I can actually use all my custom actions as well; however I'm not sure if this is safe. I would like for my user to be authenticated to do this through websocket in this DJANGO decoupled project.
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
# 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-=1l1i2&3mw2!5^zhchewl#ziv1=ip0i$jc+8n7ikryjufwsh9e'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['http://127.0.0.1:8000','127.0.0.1:8000','127.0.0.1','http://localhost:8080','ws://localhost:8080']
CORS_ALLOWED_ORIGINS = (
'http://127.0.0.1:8000',
'http://localhost:8080'
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication'
],
'DEFAULT_PERMISSION_CLASSES':[
'rest_framework.permissions.IsAuthenticated'
]
}
CSRF_TRUSTED_ORIGINS=['http://127.0.0.1:8000','http://localhost:8080']
AUTH_USER_MODEL='users.User'
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'djoser',
'corsheaders',
'daphne',
'channels',
'users',
'students',
'instructors',
'groups',
'materials',
'operation',
'channels_demultiplexer',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
# 'middleware.websocket_auth.TokenAuthMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'InterAct.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR,'templates/'),
# os.path.join(BASE_DIR,'frontend/','dist/','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 = 'InterAct.wsgi.application'
ASGI_APPLICATION = 'InterAct.asgi.application'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
# 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
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'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = 'static/'
# STATIC_ROOT= os.path.join(BASE_DIR,'publick/static/')
# STATICFILES_DIRS = [
# BASE_DIR / "static",
#
# ]
# CORS_ORIGIN_ALLOW_ALL = True
ASGI.PY
#ASGI config for InterAct 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.0/howto/deployment/asgi/
import os
from channels.auth import AuthMiddlewareStack
from middleware.websocket_auth import TokenAuthMiddleware
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
import operation.routing
from django.urls import re_path
from operation.consumers import DMultiplexer
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InterAct.settings')
django_application = get_asgi_application()
application = ProtocolTypeRouter(
{
"http": django_application,
"websocket": AllowedHostsOriginValidator(
# TokenAuthMiddleware(URLRouter([
AuthMiddlewareStack(URLRouter([
# operation.routing.websocket_urlpatterns,
# re_path(r"^/$", DMultiplexer.as_asgi()),
re_path(r"^ws/$", DMultiplexer.as_asgi()),
]))
),
}
)
CONSUMERS.PY
from djangochannelsrestframework.generics import GenericAsyncAPIConsumer
from users.models import User
from users.serializers import UserSerializer
from channels.auth import login as channels_login
from django.contrib.auth import login
from instructors.models import Instructor
from instructors.serializers import InstructorSerializer
from groups.models import Group
from groups.serializers import GroupSerializer
from students.models import Student
from students.serializers import StudentSerializer
from djangochannelsrestframework.decorators import action
from djangochannelsrestframework.observer import model_observer
from djangochannelsrestframework import permissions
from djangochannelsrestframework.observer.generics import ObserverModelInstanceMixin
from djangochannelsrestframework.mixins import (
ListModelMixin,
RetrieveModelMixin,
PatchModelMixin,
UpdateModelMixin,
CreateModelMixin,
DeleteModelMixin,
)
from channels.db import database_sync_to_async
import secrets
class Instructors_Consumer(
ListModelMixin,
# RetrieveModelMixin,
PatchModelMixin,
UpdateModelMixin,
CreateModelMixin,
DeleteModelMixin,
ObserverModelInstanceMixin,
GenericAsyncAPIConsumer):
permission_classes = (permissions.AllowAny,)
queryset=Instructor.objects.all()
serializer_class=InstructorSerializer
#action()
async def subscribe_to_instructors_feed(self, request_id, **kwargs):
print('are we all getting called?')
await self.instructors_feed.subscribe(request_id=request_id)
#model_observer(Instructor,serializer_class=InstructorSerializer)
async def instructors_feed(self, data, action, subscribing_request_ids=[], **kwargs):
# print(data,action,subscribing_request_ids)
for request_id in subscribing_request_ids:
await self.reply(data=data, action=action, request_id=request_id)
#action()
async def create_model(self, *args, **kwargs):
data=kwargs.get('data')
object=await self.create_model_db(data)
return object,200
#database_sync_to_async
def create_model_db(self,data):
print(data)
email=f'{secrets.SystemRandom().randint(1,1000000)}#{secrets.SystemRandom().randint(1,1000000)}.com'
username=secrets.SystemRandom().randint(1,1000000)
new_user=User.objects.create(first_name='first_name',last_name='last_name',role="Instructor",email=email,username=username)
new_instructor=new_user.instructor_set.first()
new_instructor=InstructorSerializer(new_instructor).data
print(new_user)
print(new_instructor)
return new_instructor
class Groups_Consumer(
ListModelMixin,
# RetrieveModelMixin,
PatchModelMixin,
UpdateModelMixin,
CreateModelMixin,
DeleteModelMixin,
ObserverModelInstanceMixin,
GenericAsyncAPIConsumer):
permission_classes = [permissions.AllowAny]
queryset=Group.objects.all()
serializer_class=GroupSerializer
#action(detail=False)
async def subscribe_to_groups_feed(self, request_id, **kwargs):
print('hello from another domain2222')
print(request_id)
print(dir(self))
# [print(thing) for thing in dir(self)]
print(self.scope.get('user'))
# login(request, user) # <- This was missing
await self.groups_feed.subscribe(request_id=request_id)
#model_observer(Group,serializer_class=GroupSerializer)
async def groups_feed(self, data, action, subscribing_request_ids=[], **kwargs):
for request_id in subscribing_request_ids:
await self.reply(data=data, action=action, request_id=request_id)
#action()
async def create_and_add_student(self, *args, **kwargs):
data=kwargs.get('data')
new_group=await self.create_and_add_student_db(data)
return new_group,200
#database_sync_to_async
def create_and_add_student_db(self,data):
pk=int(data.get('pk'))
group=Group.objects.get(pk=pk)
email=f'{secrets.SystemRandom().randint(1,1000000)}#{secrets.SystemRandom().randint(1,1000000)}.com'
username=secrets.SystemRandom().randint(1,1000000)
first_name=secrets.SystemRandom().randint(1,1000000)
new_user=User.objects.create(first_name=first_name,role="Student",email=email,username=username)
new_student=new_user.student_set.first()
group.student.add(new_student)
group.save()
new_student=StudentSerializer(new_student).data
response={
'new_student':new_student,
'parent':pk,
}
return response
class Students_Consumer(
ListModelMixin,
# RetrieveModelMixin,
PatchModelMixin,
UpdateModelMixin,
CreateModelMixin,
DeleteModelMixin,
ObserverModelInstanceMixin,
GenericAsyncAPIConsumer):
permission_classes = (permissions.AllowAny,)
queryset=Student.objects.all()
serializer_class=StudentSerializer
#action()
async def subscribe_to_students_feed(self, request_id, **kwargs):
await self.students_feed.subscribe(request_id=request_id)
#model_observer(Student,serializer_class=StudentSerializer)
async def students_feed(self, data, action, subscribing_request_ids=[], **kwargs):
# print(data,action,subscribing_request_ids)
for request_id in subscribing_request_ids:
await self.reply(data=data, action=action, request_id=request_id)
class Users_Consumer(
ListModelMixin,
# RetrieveModelMixin,
PatchModelMixin,
UpdateModelMixin,
CreateModelMixin,
DeleteModelMixin,
ObserverModelInstanceMixin,
GenericAsyncAPIConsumer):
queryset=User.objects.all()
serializer_class=UserSerializer
permission_classes = (permissions.AllowAny,)
#action()
async def subscribe_to_users_feed(self, request_id, **kwargs):
print('hello from another domain')
await self.users_feed.subscribe(request_id=request_id)
#model_observer(User,serializer_class=UserSerializer)
async def users_feed(self, data, action, subscribing_request_ids=[], **kwargs):
# print(data,action,subscribing_request_ids)
for request_id in subscribing_request_ids:
await self.reply(data=data, action=action, request_id=request_id)
from channels_demultiplexer.demultiplexer import WebsocketDemultiplexer
class DMultiplexer(WebsocketDemultiplexer):
consumer_classes={
"users":Users_Consumer,
"groups":Groups_Consumer,
"instructors":Instructors_Consumer,
"students":Students_Consumer,
}
in my VUEX STORE index.js I have this action:
async openEndpoint(state,params){
console.log(params);
let ws=params.ws
let endpoint=params.endpoint
ws.onopen=async ()=>{
console.log('connection established');
console.log(endpoint);
let data={
stream:endpoint,
payload:{
action:'subscribe_to_groups_feed',
request_id: new Date().getTime()
}
}
console.log(ws);
ws.onmessage=(ws_event)=>{
let response=JSON.parse(ws_event.data)
console.log(response);
console.log(response.payload.errors);
}
ws.send(JSON.stringify(data))
}
}

After hours of researching it turns out that I can write middleware to incercept the websocket requests. I had to pass the token as a query_string parameter and then decode it in the middleware so that I could then get the user from the Token.objects provided by Djoser.
However now all my normal fetch requests are failing. I provide the Middleware for anyone trying to advance in this topic.
from channels.auth import AuthMiddleware
from channels.db import database_sync_to_async
from channels.sessions import CookieMiddleware, SessionMiddleware
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
#database_sync_to_async
def get_user(scope_token):
token = scope_token.decode('utf8')
token= token.split('=')[1]
print(token)
if not token:
return AnonymousUser()
try:
user = Token.objects.get(key=token).user
except Exception as exception:
return AnonymousUser()
if not user.is_active:
return AnonymousUser()
return user
class TokenAuthMiddleware(AuthMiddleware):
def __init__(self, app):
# Store the ASGI application we were passed
self.app = app
async def __call__(self, scope,receive,send,*args,**kwargs):
# Look up user from query string (you should also do things like
# checking if it is a valid user ID, or if scope["user"] is already
# populated).
print('MIDDLEWARING')
scope['user'] = await get_user(scope["query_string"])
return await self.app(scope,receive,send)
async def resolve_scope(self, scope,receive,send):
scope['user']._wrapped = await get_user(scope)
def TokenAuthMiddlewareStack(inner):
return CookieMiddleware(SessionMiddleware(TokenAuthMiddleware(inner)))
UPDATE:
On applying the above solution I stopped being able to get my axios/fetch requests to work. I suppose this has to do with the fact that I used the middleware to intercept websocket requests. When I undo all the middleware changes everything works normal. The 'trick' around this was to just disable the middleware in settings.py somehow this allows the axios/fetch requests to go through django application normally and still incercepts the websocket requests. This is how the settings look like now.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 'middleware.websocket_auth.TokenAuthMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Related

How to differentiate my views in DRF swagger (yasg)?

I have an api made with djangorestframework and I want to add a swagger. It works well. In my api I have some views that require authentication with Basic or JWT so I set this up in my settings. The problem is that in my swagger gui I do not manage to tell which view is not requiring authentication. Do you know how to do that.
#api/urls.py
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact#snippets.local"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
path('mesures/', views.get_all),
path('mesure-add/', views.add_mesure),
path('token/', TokenObtainPairView.as_view(), name='obtain_tokens'),
path('token/refresh/', TokenRefreshView.as_view(), name='refresh_token'),
path('api-auth/', include('rest_framework.urls')),
path('register/', views.register_user),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
In my settings.py
# in my settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=int(os.getenv("TOKEN_DAYS_LIFE"))),
}
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Basic': {
'type': 'basic'
},
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
}
}
In my views.py
#views.py
#swagger_auto_schema(methods=['post'], request_body=MesureSerializer)
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def add_mesure(request):
serializer = MesureSerializer(data=request.data, context={'request':request})
# this is to provide the user automatically from token auth
# no need to provide it from the post request
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer._errors)
#swagger_auto_schema(methods=['post'], request_body=UserSerializer)
#api_view(['POST'])
def register_user(request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
as you can see my view register_user should not show a authentication in my swagger but this is not the case.
It is ok, I just needed to use the security=[] in #swagger_auto_schema for the specific view I want to tell there is no authentication required.
#swagger_auto_schema(methods=['post'], request_body=UserSerializer, security=[])

zappa wagtail how to use s3 for media/images (server error 500)

Im using wagtail on an aws lambda with zappa.
Everything is working fine except the images i'm trying to upload in the blog posts.
That is to say when i try to upload an image in the /cms-admin in the images menu i get a server error 500 message.
I have configured the images to be stored on s3 following this post https://wagtail.org/blog/amazon-s3-for-media-files/ but it seems there is a problem.
Here is my models.py :
from django.db import models
from wagtail.core.models import Page
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.snippets.models import register_snippet
from taggit.models import Tag as TaggitTag
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase
from modelcluster.tags import ClusterTaggableManager
from wagtail.admin.edit_handlers import (FieldPanel,
FieldRowPanel,
InlinePanel,
MultiFieldPanel,
PageChooserPanel,
StreamFieldPanel,
)
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.core.fields import StreamField
from .blocks import BodyBlock
class BlogPage(Page):
description = models.CharField(max_length=255, blank=True,)
content_panels = Page.content_panels + [FieldPanel("description", classname="full")]
class PostPage(Page):
header_image = models.ForeignKey(
"wagtailimages.Image", null=True,
blank=True, on_delete=models.SET_NULL, related_name="+",)
body = StreamField(BodyBlock(), blank=True)
tags = ClusterTaggableManager(through="blog.PostPageTag", blank=True)
content_panels = Page.content_panels + [ ImageChooserPanel("header_image"), InlinePanel("categories", label="category"), FieldPanel("tags"),StreamFieldPanel("body"),]
#register_snippet
class BlogCategory(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=80)
panels = [ FieldPanel("name"),
FieldPanel("slug"),]
def __str__(self):
return self.name
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
#register_snippet
class Tag(TaggitTag):
class Meta:
proxy = True
class PostPageBlogCategory(models.Model):
page = ParentalKey("blog.PostPage", on_delete=models.CASCADE, related_name="categories" )
blog_category = models.ForeignKey("blog.BlogCategory", on_delete=models.CASCADE, related_name="post_pages")
panels = [ SnippetChooserPanel("blog_category"),]
class Meta:
unique_together = ("page", "blog_category")
class PostPageTag(TaggedItemBase):
content_object = ParentalKey("PostPage", related_name="post_tags")
Here is my settings.py
"""
Django settings for react_wagtail_app project.
Generated by 'django-admin startproject' using Django 4.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
from pathlib import Path
import os
# 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-jvstu05lh1fp2ow26blj&5%x_7bb(e*qt#(urj+$(2n&1i&)ka'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['xwovkgc3f6.execute-api.eu-west-3.amazonaws.com']
# Application definition
INSTALLED_APPS = [ 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
"wagtail.embeds",
"wagtail.sites",
"wagtail.users",
"wagtail.snippets",
"wagtail.documents",
"wagtail.images",
"wagtail.search",
"wagtail.admin",
"wagtail.core",
"modelcluster",
"taggit",
"blog",
"storages"]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
"whitenoise.middleware.WhiteNoiseMiddleware",
'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',
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
]
ROOT_URLCONF = 'react_wagtail_app.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 = 'react_wagtail_app.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'spa',
'USER': 'postgres',
'PASSWORD': 'password',
'HOST': '146.190.238.8',
'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',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = 'fr-FR'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
AWS_STORAGE_BUCKET_NAME = "zappa-pasvx3uig"
AWS_ACCESS_KEY_ID = 'secret'
AWS_SECRET_ACCESS_KEY = 'secret'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
#MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "/dev/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
WHITENOISE_STATIC_PREFIX = "/static/"
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
#MEDIA_ROOT = str(BASE_DIR / 'media')
WAGTAIL_SITE_NAME = 'My Project'
I have seen in other posts here: Configuring Django/Wagtail media to use S3
that a special class is used inherited from S3BotoStorage. I was wondering if i didnt need to use that in my models.py for the Images?
from django.conf import settings
from storages.backends.s3boto import S3BotoStorage
class MediaStorage(S3Boto3Storage):
location = settings.MEDIAFILES_LOCATION
Could you help me understand what is not properly configured so the images are properly uploaded to s3 and print on the screen normally
I ve tried to set AWS_DEFAULT_ACL='public-read' following another stackoverflow post but it is the same.
zappa tail gives me the following error :
botocore.exceptions.ClientError: An error occurred (InvalidToken) when calling the PutObject operation: The provided token is malformed or otherwise invalid.
I ve managed to overcome this error by providing two separate credentials
AWS_S3_ACCESS_KEY_ID
and AWS_ACCESS_KEY_ID
but i'm still having problems with the images
I'm considering giving up on aws lambda with zappa and switching to a container service
I am afraid I don't know anything about Zappa. We use ElasticContainerService running on EC2 instances we manage. We are considering going to ECS on Fargate but have not made the transition yet.
Looking at your settings file CHANGE YOUR AWS CREDENTIALS AT ONCE!!!!
After that, I am fairly sure you need the MEDIA_ROOT and MEDIA_URL settings. Ours are set to MEDIA_ROOT = '/media' and MEDIA_URL = '/media/'. And we do have AWS_DEFAULT_ACL = 'public-read' so that images can be served straight from S3 using the default for WAGTAILDOCS_SERVE_METHOD of 'redirect'
Until you get s3 storage working, I would remove the AWS_S3_CUSTOM_DOMAIN setting so you can debug one thing at a time.

cannot set new password using django-graphql-auth

Graphene sends the email, but the url doesn't exist. How should I set up the token url for this?
I can't find docs on how to configure urls.py so that the link that it sends through the email works.
http://127.0.0.1:8090/activate/eyJ1c2VybmFtZSI6ImtkZWVuZXl6eiIsImFjdGlvbiI6ImFjdGl2YXRpb24ifQ:1m2a0v:04V3Ho0msVn7nHuFW469DC9GBYuUz2czfsFai09EOyM
settings.py
GRAPHQL_JWT = {
'JWT_VERIFY_EXPIRATION': True,
'JWT_LONG_RUNNING_REFRESH_TOKEN': True,
'ALLOW_LOGIN_NOT_VERIFIED': True,
'JWT_ALLOW_ARGUMENT': True,
"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",
"graphql_auth.mutations.VerifySecondaryEmail",
],
}
schema.py
class AuthMutation(graphene.ObjectType):
register = mutations.Register.Field()
verify_account = mutations.VerifyAccount.Field()
token_auth = mutations.ObtainJSONWebToken.Field()
update_account = mutations.UpdateAccount.Field()
resend_activation_email = mutations.ResendActivationEmail.Field()
send_password_reset_email = mutations.SendPasswordResetEmail.Field()
password_reset = mutations.PasswordReset.Field()
password_change = mutations.PasswordChange.Field()
You need to create a view to handle this url.
from django.http import HttpResponseRedirect
from django.views import View
from graphql_auth.models import UserStatus
class ActivateView(View):
def get(self, request, **kwargs):
try:
token = kwargs.get("token")
UserStatus.verify(token)
except Exception:
return HttpResponseRedirect(f"/some/error/url")
return HttpResponseRedirect(f"/activate/thankyou")
And in your urls.py
urlpatterns = [
....
path("activate/<str:token>", ActivateView.as_view()),
...
]

Add documentation to generics.RetrieveAPIView 'retrieve' method's query param

I have a simple view which takes 'email' as a query param and I would like to have it documented in the OpenAPI autogenerated schema. So far I tried applying method_decorator together with swagger_auto_schema on the API View class definition, but without success:
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.utils.decorators import method_decorator
#method_decorator(name='retrieve', decorator=swagger_auto_schema(manual_parameters=[
openapi.Parameter('email', openapi.IN_QUERY, description="Email to be checked", type=openapi.TYPE_STRING)]))
class EmailCheckView(generics.RetrieveAPIView):
serializer_class = EmailCheckSerializer
def get_queryset(self):
email = self.request.query_params.get('email', None)
if not email:
raise Http404
return User.objects.filter(email=self.kwargs['email'])
the auto generated models contains only information on the body coming from the serializer. Any ideas what's wrong?
DRF: 3.12.2
drf-yasg: 1.20.0
My swagger schema is added in urls.py with:
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="My API",
default_version='v1',
description="",
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
...
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
...
]
Change
name='retrieve'
to
name='get'
#method_decorator(
name="get", # change is here
decorator=swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
"email",
openapi.IN_QUERY,
description="Email to be checked",
type=openapi.TYPE_STRING,
)
]
),
)
class EmailCheckView(generics.RetrieveAPIView):
serializer_class = EmailCheckSerializer
def get_queryset(self):
email = self.request.query_params.get("email", None)
if not email:
raise Http404
return User.objects.filter(email=self.kwargs["email"])
Note: I am not sure whether the issue belongs to method_decorator(...) or drf-yasg itself

Authentication credentials were not provided - Django

I'm having trouble with this error. Tried almost all available solutions but nothing working for me. At frontend side I am using Angular 6 and I am pretty sure it's not error from it. Hoping for a response soon and thanks in advance guys.
register/url.py
from django.urls import path, include
from rest_framework import routers
from . import views
from rest_framework.authtoken.views import ObtainAuthToken
router = routers.DefaultRouter()
router.register('users', views.UserViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
#path('auth/', include('rest_framework.urls', namespace='rest_framework')),
path('auth/', ObtainAuthToken.as_view()),
]
serialier.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = { 'password' : { 'write_only' : True , 'required':True } }
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
view.py
from django.contrib.auth.models import User
from rest_framework import viewsets
from .serializers import UserSerializer
from rest_framework.permissions import IsAuthenticated
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
authentication_classes = (TokenAuthentication, SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
setting.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'register',
'corsheaders',
]
The following error is displayed in the browser's console:
{“detail”:“Authentication credentials were not provided.”}
Your viewset has the IsAuthenticated permission class. In other words, an user must be authenticated in order to retrieve, update or even create an instance. Make sure that the appropriate headers are included in your requests.
For example, for a token authentication, as stated by Django Rest Framework documentation
For clients to authenticate, the token key should be included in the
Authorization HTTP header. The key should be prefixed by the string literal
"Token", with whitespace separating the two strings. For example:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
In the particular case of an account creation, I'm not sure that it is intended for your application to require an user authentication.

Resources