Make sentry errors more useful

This commit is contained in:
Ramon Wenger 2023-03-30 13:51:34 +02:00
parent d846e76245
commit 8bdf17685a
4 changed files with 298 additions and 248 deletions

View File

@ -2,24 +2,31 @@ from django.conf import settings
from django.conf.urls import url from django.conf.urls import url
from django.urls import include from django.urls import include
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from api.schema_public import schema from api.schema_public import schema
from core.views import PrivateGraphQLView from core.views import SentryGraphQLView, PrivateGraphQLView
app_name = 'api' app_name = "api"
urlpatterns = [ urlpatterns = [
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))), url(r"^graphql-public", csrf_exempt(SentryGraphQLView.as_view(schema=schema))),
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())), url(r"^graphql", csrf_exempt(PrivateGraphQLView.as_view())),
# oauth # oauth
url(r'^oauth/', include('oauth.urls', namespace="oauth")), url(r"^oauth/", include("oauth.urls", namespace="oauth")),
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True, urlpatterns += [
pretty=True)))] url(
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))] r"^graphiql-public",
csrf_exempt(
SentryGraphQLView.as_view(schema=schema, graphiql=True, pretty=True)
),
)
]
urlpatterns += [
url(
r"^graphiql",
csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)),
)
]

View File

@ -4,6 +4,8 @@ from django.conf import settings
from django.http import Http404, HttpResponsePermanentRedirect from django.http import Http404, HttpResponsePermanentRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from graphene import ResolveInfo
from sentry_sdk import capture_exception
try: try:
@ -15,25 +17,25 @@ _thread_locals = local()
def get_current_request(): def get_current_request():
""" returns the request object for this thread """ """returns the request object for this thread"""
return getattr(_thread_locals, "request", None) return getattr(_thread_locals, "request", None)
def get_current_user(): def get_current_user():
""" returns the current user, if exist, otherwise returns None """ """returns the current user, if exist, otherwise returns None"""
request = get_current_request() request = get_current_request()
if request: if request:
return getattr(request, "user", None) return getattr(request, "user", None)
class ThreadLocalMiddleware(MiddlewareMixin): class ThreadLocalMiddleware(MiddlewareMixin):
""" Simple middleware that adds the request object in thread local storage.""" """Simple middleware that adds the request object in thread local storage."""
def process_request(self, request): def process_request(self, request):
_thread_locals.request = request _thread_locals.request = request
def process_response(self, request, response): def process_response(self, request, response):
if hasattr(_thread_locals, 'request'): if hasattr(_thread_locals, "request"):
del _thread_locals.request del _thread_locals.request
return response return response
@ -42,9 +44,14 @@ class CommonRedirectMiddleware(MiddlewareMixin):
""" """
redirects common bad requests or missing images redirects common bad requests or missing images
""" """
DEFAULT_REDIRECTS = [ DEFAULT_REDIRECTS = [
(re.compile("(?i)(.+\.php|.+wp-admin.+|.+\.htm|\/wordpress\/|\/wp\/|\/mm5\/|\/wp-content\/)"), (
'http://example.org/'), re.compile(
"(?i)(.+\.php|.+wp-admin.+|.+\.htm|\/wordpress\/|\/wp\/|\/mm5\/|\/wp-content\/)"
),
"http://example.org/",
),
] ]
def process_exception(self, request, exception): def process_exception(self, request, exception):
@ -72,10 +79,17 @@ class CommonRedirectMiddleware(MiddlewareMixin):
# some static image is missing show a placeholder (use full for local dev) # some static image is missing show a placeholder (use full for local dev)
m = re.match(r".*(max|fill)-(?P<dimensions>\d+x\d+)\.(jpg|png|svg)", path) m = re.match(r".*(max|fill)-(?P<dimensions>\d+x\d+)\.(jpg|png|svg)", path)
if m: if m:
return 'https://picsum.photos/{}'.format(m.group('dimensions').replace('x', '/')) return "https://picsum.photos/{}".format(
m.group("dimensions").replace("x", "/")
)
# or dummy image: return 'http://via.placeholder.com/{}'.format(m.group('dimensions')) # or dummy image: return 'http://via.placeholder.com/{}'.format(m.group('dimensions'))
if '.png' in path or '.jpg' in path or '.svg' in path or 'not-found' in path: if (
return 'https://picsum.photos/400/400' ".png" in path
or ".jpg" in path
or ".svg" in path
or "not-found" in path
):
return "https://picsum.photos/400/400"
# https://stackoverflow.com/questions/4898408/how-to-set-a-login-cookie-in-django # https://stackoverflow.com/questions/4898408/how-to-set-a-login-cookie-in-django
@ -86,14 +100,24 @@ class UserLoggedInCookieMiddleWare(MiddlewareMixin):
If the user is not authenticated and the cookie remains, delete it If the user is not authenticated and the cookie remains, delete it
""" """
cookie_name = 'loginStatus' cookie_name = "loginStatus"
def process_response(self, request, response): def process_response(self, request, response):
#if user and no cookie, set cookie # if user and no cookie, set cookie
if request.user.is_authenticated and not request.COOKIES.get(self.cookie_name): if request.user.is_authenticated and not request.COOKIES.get(self.cookie_name):
response.set_cookie(self.cookie_name, 'true') response.set_cookie(self.cookie_name, "true")
elif not request.user.is_authenticated and request.COOKIES.get(self.cookie_name): elif not request.user.is_authenticated and request.COOKIES.get(
#else if if no user and cookie remove user cookie, logout self.cookie_name
):
# else if if no user and cookie remove user cookie, logout
response.delete_cookie(self.cookie_name) response.delete_cookie(self.cookie_name)
return response return response
class SentryMiddleware(object):
def on_error(self, error):
capture_exception(error)
raise error
def resolve(self, next, root, info: ResolveInfo, **args):
return next(root, info, **args).catch(self.on_error)

View File

@ -16,142 +16,138 @@ load_dotenv(find_dotenv())
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY') SECRET_KEY = os.environ.get("SECRET_KEY")
SIGNING_SECRET = os.environ.get('SIGNING_SECRET') SIGNING_SECRET = os.environ.get("SIGNING_SECRET")
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool_value(os.environ.get('DEBUG', '')) DEBUG = bool_value(os.environ.get("DEBUG", ""))
TEST = 'test' in sys.argv TEST = "test" in sys.argv
ENABLE_SILKY = bool_value(os.environ.get('ENABLE_SILKY', '')) ENABLE_SILKY = bool_value(os.environ.get("ENABLE_SILKY", ""))
SERVE_VIA_WEBPACK = bool_value(os.environ.get('SERVE_VIA_WEBPACK', DEBUG)) SERVE_VIA_WEBPACK = bool_value(os.environ.get("SERVE_VIA_WEBPACK", DEBUG))
ENABLE_SENTRY = not DEBUG or bool_value(os.environ.get('ENABLE_SENTRY_DEBUG', '')) ENABLE_SENTRY = not DEBUG or bool_value(os.environ.get("ENABLE_SENTRY_DEBUG", ""))
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ["*"]
if not DEBUG: if not DEBUG:
SECURE_SSL_REDIRECT = True SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'core', "core",
'api', "api",
'users', "users",
'books', "books",
'objectives', "objectives",
'rooms', "rooms",
'assignments', "assignments",
'basicknowledge', "basicknowledge",
'portfolio', "portfolio",
'statistics', "statistics",
'surveys', "surveys",
'notes', "notes",
'news', "news",
'oauth', "oauth",
"wagtail.contrib.redirects",
'wagtail.contrib.redirects', "wagtail.contrib.modeladmin",
'wagtail.contrib.modeladmin', "wagtail.embeds",
'wagtail.embeds', "wagtail.sites",
'wagtail.sites', "wagtail.users",
'wagtail.users', "wagtail.snippets",
'wagtail.snippets', "wagtail.documents",
'wagtail.documents', "wagtail.images",
'wagtail.images', "wagtail.search",
'wagtail.search', "wagtail.admin",
'wagtail.admin', "wagtail",
'wagtail', "wagtail.api.v2",
'wagtail.api.v2', "wagtailautocomplete",
'wagtailautocomplete', "taggit",
"django.contrib.admin",
'taggit', "django.contrib.auth",
"django.contrib.contenttypes",
'django.contrib.admin', "django.contrib.sessions",
'django.contrib.auth', "django.contrib.messages",
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
# 'raven.contrib.django.raven_compat', # 'raven.contrib.django.raven_compat',
'whitenoise.runserver_nostatic', "whitenoise.runserver_nostatic",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django_filters', "django_filters",
'graphene_django', "graphene_django",
'django_extensions', "django_extensions",
'compressor', "compressor",
] ]
if DEBUG: if DEBUG:
INSTALLED_APPS += ['wagtail.contrib.styleguide'] INSTALLED_APPS += ["wagtail.contrib.styleguide"]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'whitenoise.middleware.WhiteNoiseMiddleware', "whitenoise.middleware.WhiteNoiseMiddleware",
'django.middleware.gzip.GZipMiddleware', "django.middleware.gzip.GZipMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.locale.LocaleMiddleware' "django.middleware.locale.LocaleMiddleware",
] ]
# Enable CORS for local development # Enable CORS for local development
if DEBUG: if DEBUG:
INSTALLED_APPS += ['corsheaders'] INSTALLED_APPS += ["corsheaders"]
MIDDLEWARE += ['corsheaders.middleware.CorsMiddleware'] MIDDLEWARE += ["corsheaders.middleware.CorsMiddleware"]
CORS_ORIGIN_WHITELIST = ( CORS_ORIGIN_WHITELIST = ("http://localhost:8080",)
'http://localhost:8080',
)
CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_CREDENTIALS = True
# enable silk for performance measuring # enable silk for performance measuring
if ENABLE_SILKY: if ENABLE_SILKY:
INSTALLED_APPS += ['silk'] INSTALLED_APPS += ["silk"]
MIDDLEWARE += ['silk.middleware.SilkyMiddleware', ] MIDDLEWARE += [
"silk.middleware.SilkyMiddleware",
]
MIDDLEWARE += [ MIDDLEWARE += [
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
'wagtail.contrib.redirects.middleware.RedirectMiddleware', "core.middleware.ThreadLocalMiddleware",
"core.middleware.CommonRedirectMiddleware",
'core.middleware.ThreadLocalMiddleware', "core.middleware.UserLoggedInCookieMiddleWare",
'core.middleware.CommonRedirectMiddleware', "oauth.middleware.user_has_license_middleware",
'core.middleware.UserLoggedInCookieMiddleWare',
'oauth.middleware.user_has_license_middleware',
] ]
ROOT_URLCONF = 'core.urls' ROOT_URLCONF = "core.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [os.path.join(BASE_DIR, '..', 'client/dist'), os.path.join(BASE_DIR, 'templates')], "DIRS": [
'APP_DIRS': True, os.path.join(BASE_DIR, "..", "client/dist"),
'OPTIONS': { os.path.join(BASE_DIR, "templates"),
'context_processors': [ ],
'django.template.context_processors.debug', "APP_DIRS": True,
'django.template.context_processors.request', "OPTIONS": {
'django.contrib.auth.context_processors.auth', "context_processors": [
'django.contrib.messages.context_processors.messages', "django.template.context_processors.debug",
'core.context_processors.settings_context', "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"core.context_processors.settings_context",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'core.wsgi.application' WSGI_APPLICATION = "core.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
# Database # Database
DATABASES = { DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
'default': dj_database_url.config(conn_max_age=600)
}
# Django custom user # Django custom user
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = "users.User"
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
@ -162,30 +158,30 @@ if WEAK_PASSWORDS:
else: else:
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
LOGOUT_REDIRECT_URL = '/login' LOGOUT_REDIRECT_URL = "/login"
LOGIN_REDIRECT_URL = '/login' LOGIN_REDIRECT_URL = "/login"
LOGIN_URL = LOGIN_REDIRECT_URL LOGIN_URL = LOGIN_REDIRECT_URL
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'de' LANGUAGE_CODE = "de"
TIME_ZONE = 'UTC' TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@ -194,167 +190,161 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LANGUAGES = [ LANGUAGES = [
('de', _('German')), ("de", _("German")),
('en', _('English')), ("en", _("English")),
] ]
LOCALE_PATHS = [ LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
os.path.join(BASE_DIR, 'locale')
]
# Honor the 'X-Forwarded-Proto' header for request.is_secure() # Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/ # https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATIC_URL = '/static/' STATIC_URL = "/static/"
STATICFILES_DIRS = ( STATICFILES_DIRS = (
os.path.join(BASE_DIR, '..', 'client/dist/static'), os.path.join(BASE_DIR, "..", "client/dist/static"),
os.path.join(BASE_DIR, '..', 'client/src/assets'), os.path.join(BASE_DIR, "..", "client/src/assets"),
) )
if not TEST: if not TEST:
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
COMPRESS_CSS_FILTERS = [ COMPRESS_CSS_FILTERS = [
# 'django_compressor_autoprefixer.AutoprefixerFilter', # 'django_compressor_autoprefixer.AutoprefixerFilter',
'compressor.filters.cssmin.CSSMinFilter', "compressor.filters.cssmin.CSSMinFilter",
] ]
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
'compressor.finders.CompressorFinder', "compressor.finders.CompressorFinder",
) )
COMPRESS_PRECOMPILERS = ( COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
('text/x-scss', 'django_libsass.SassCompiler'),
)
COMPRESS_ENABLED = True COMPRESS_ENABLED = True
if not DEBUG: if not DEBUG:
COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage' COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
COMPRESS_OFFLINE = True COMPRESS_OFFLINE = True
# AWS S3 # AWS S3
# http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html # http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html
USE_AWS = bool_value(os.environ.get('USE_AWS')) USE_AWS = bool_value(os.environ.get("USE_AWS"))
AWS_QUERYSTRING_AUTH = False AWS_QUERYSTRING_AUTH = False
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME') AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")
AWS_S3_FILE_OVERWRITE = False AWS_S3_FILE_OVERWRITE = False
# use with cloudfront # use with cloudfront
AWS_S3_CUSTOM_DOMAIN = '{}.s3-{}.amazonaws.com'.format(AWS_STORAGE_BUCKET_NAME, AWS_S3_CUSTOM_DOMAIN = "{}.s3-{}.amazonaws.com".format(
os.environ.get('AWS_REGION', 'eu-west-1')) AWS_STORAGE_BUCKET_NAME, os.environ.get("AWS_REGION", "eu-west-1")
)
if USE_AWS: if USE_AWS:
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# use with cloudfront # use with cloudfront
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
else: else:
MEDIA_URL = '/media/' MEDIA_URL = "/media/"
MEDIA_ROOT = os.environ.get('DJANGO_MEDIAFILES', os.path.join(BASE_DIR, 'media')) MEDIA_ROOT = os.environ.get("DJANGO_MEDIAFILES", os.path.join(BASE_DIR, "media"))
AWS_S3_OBJECT_PARAMETERS = { AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400', "CacheControl": "max-age=86400",
} }
# Media Files # Media Files
USE_404_FALLBACK_IMAGE = bool_value(os.environ.get('USE_404_FALLBACK_IMAGE', 'True')) USE_404_FALLBACK_IMAGE = bool_value(os.environ.get("USE_404_FALLBACK_IMAGE", "True"))
# Logging Conf # Logging Conf
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'formatters': { "formatters": {
'verbose_format': { "verbose_format": {
'format': '%(levelname)s %(asctime)s %(module)s %(name)s (%(process)d): %(message)s' "format": "%(levelname)s %(asctime)s %(module)s %(name)s (%(process)d): %(message)s"
},
'simple_format': {
'format': '%(levelname)s %(name)s: %(message)s'
}, },
"simple_format": {"format": "%(levelname)s %(name)s: %(message)s"},
}, },
'disable_existing_loggers': not DEBUG, "disable_existing_loggers": not DEBUG,
'handlers': { "handlers": {
'mail_admins': { "mail_admins": {
'level': 'CRITICAL', "level": "CRITICAL",
'class': 'django.utils.log.AdminEmailHandler' "class": "django.utils.log.AdminEmailHandler",
}, },
'console': { "console": {
'level': 'DEBUG', "level": "DEBUG",
'class': 'logging.StreamHandler', "class": "logging.StreamHandler",
'stream': sys.stdout, "stream": sys.stdout,
'formatter': 'simple_format' "formatter": "simple_format",
}, },
# for automatic papertrail logging # for automatic papertrail logging
'SysLog': { "SysLog": {
'level': 'INFO', "level": "INFO",
'class': 'logging.handlers.SysLogHandler', "class": "logging.handlers.SysLogHandler",
'formatter': 'simple_format', "formatter": "simple_format",
}, },
}, },
'loggers': { "loggers": {
'': { "": {"handlers": ["console"], "level": "WARNING"},
'handlers': ['console'], "skillbox": {
'level': 'WARNING' "handlers": ["console", "SysLog"],
"level": os.environ.get("DJANGO_LOG_LEVEL", "INFO"),
"propagate": False,
}, },
'skillbox': { "graphql": {"handlers": ["console"], "level": "WARNING", "propagate": False},
'handlers': ['console', 'SysLog'], "django": {
'level': os.environ.get('DJANGO_LOG_LEVEL', 'INFO'), "handlers": ["console"],
'propagate': False "level": "INFO",
"propagate": False,
}, },
'graphql': { "django.server": {
'handlers': ['console'], "handlers": ["console"],
'level': 'WARNING', "level": "WARNING",
'propagate': False "propagate": False,
}, },
'django': { },
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
'django.server': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': False,
},
}
} }
if ENABLE_SENTRY and os.environ.get('SENTRY_DSN'): if ENABLE_SENTRY and os.environ.get("SENTRY_DSN"):
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger
def before_send(event, hint): def before_send(event, hint):
user = event['user'] user = event["user"]
id = user['id'] id = user["id"]
event['user'] = {'id': id} event["user"] = {"id": id}
return event return event
environment = os.environ.get("SENTRY_ENV", "localhost")
environment = os.environ.get('SENTRY_ENV', 'localhost') sample_rate = os.environ.get("SENTRY_SAMPLE_RATE", 0.01)
sentry_sdk.init( sentry_sdk.init(
dsn=os.environ.get('SENTRY_DSN'), dsn=os.environ.get("SENTRY_DSN"),
integrations=[DjangoIntegration()], integrations=[DjangoIntegration()],
send_default_pii=True, send_default_pii=True,
before_send=before_send, before_send=before_send,
environment=environment before_send_transaction=before_send,
environment=environment,
traces_sample_rate=sample_rate,
) )
RAVEN_DSN_JS = os.environ.get('RAVEN_DSN_JS', '') # ignore 'Traceback' error messages, they don't give enough information
# from https://jerrynsh.com/how-to-monitor-python-graphql-api-with-sentry/
ignore_logger("graphql.execution.utils")
RAVEN_DSN_JS = os.environ.get("RAVEN_DSN_JS", "")
GRAPHENE = { GRAPHENE = {
'SCHEMA': 'api.schema.schema', "SCHEMA": "api.schema.schema",
'SCHEMA_OUTPUT': 'schema.graphql' "SCHEMA_OUTPUT": "schema.graphql",
"MIDDLEWARE": ["core.middleware.SentryMiddleware"],
} }
# if DEBUG: # if DEBUG:
@ -363,25 +353,27 @@ GRAPHENE = {
# ] # ]
# http://docs.wagtail.io/en/v2.1/advanced_topics/settings.html?highlight=urls # http://docs.wagtail.io/en/v2.1/advanced_topics/settings.html?highlight=urls
WAGTAIL_SITE_NAME = 'skillbox' WAGTAIL_SITE_NAME = "skillbox"
WAGTAILSEARCH_BACKENDS = { WAGTAILSEARCH_BACKENDS = {
'default': { "default": {
'BACKEND': 'wagtail.search.backends.database', "BACKEND": "wagtail.search.backends.database",
} }
} }
WAGTAILDOCS_DOCUMENT_MODEL = 'books.CustomDocument' WAGTAILDOCS_DOCUMENT_MODEL = "books.CustomDocument"
GRAPHQL_QUERIES_DIR = os.path.join(BASE_DIR, '..', 'client', 'src', 'graphql', 'gql', 'queries') GRAPHQL_QUERIES_DIR = os.path.join(
GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, '../mutations') BASE_DIR, "..", "client", "src", "graphql", "gql", "queries"
)
GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, "../mutations")
DEFAULT_FROM_EMAIL = 'myskillbox <noreply@myskillbox.ch>' DEFAULT_FROM_EMAIL = "myskillbox <noreply@myskillbox.ch>"
# Metanet Config # Metanet Config
if DEBUG: if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
else: else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
ALLOW_BETA_LOGIN = True ALLOW_BETA_LOGIN = True
@ -390,25 +382,25 @@ HEP_URL = os.environ.get("HEP_URL")
# HEP Oauth # HEP Oauth
AUTHLIB_OAUTH_CLIENTS = { AUTHLIB_OAUTH_CLIENTS = {
'hep': { "hep": {
'client_id': os.environ.get("OAUTH_CLIENT_ID"), "client_id": os.environ.get("OAUTH_CLIENT_ID"),
'client_secret': os.environ.get("OAUTH_CLIENT_SECRET"), "client_secret": os.environ.get("OAUTH_CLIENT_SECRET"),
'request_token_url': None, "request_token_url": None,
'request_token_params': None, "request_token_params": None,
'access_token_url': os.environ.get("OAUTH_ACCESS_TOKEN_URL"), "access_token_url": os.environ.get("OAUTH_ACCESS_TOKEN_URL"),
'access_token_params': None, "access_token_params": None,
'refresh_token_url': None, "refresh_token_url": None,
'authorize_url': os.environ.get("OAUTH_AUTHORIZE_URL"), "authorize_url": os.environ.get("OAUTH_AUTHORIZE_URL"),
'api_base_url': os.environ.get("OAUTH_API_BASE_URL"), "api_base_url": os.environ.get("OAUTH_API_BASE_URL"),
'client_kwargs': { "client_kwargs": {
'scope': 'orders', "scope": "orders",
'token_endpoint_auth_method': 'client_secret_post', "token_endpoint_auth_method": "client_secret_post",
'token_placement': 'header', "token_placement": "header",
} },
} }
} }
PLATFORM = os.environ.get('APP_FLAVOR', 'myskillbox') PLATFORM = os.environ.get("APP_FLAVOR", "myskillbox")
OAUTH_LOCAL_REDIRECT_URI = os.environ.get("OAUTH_LOCAL_REDIRECT_URI") OAUTH_LOCAL_REDIRECT_URI = os.environ.get("OAUTH_LOCAL_REDIRECT_URI")
@ -419,12 +411,12 @@ TASKBASE_SUPERPASSWORD = os.environ.get("TASKBASE_SUPERPASSWORD")
TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL") TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
ENABLE_SPELLCHECK = True if TASKBASE_BASEURL else False ENABLE_SPELLCHECK = True if TASKBASE_BASEURL else False
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
TEST_OUTPUT_DIR = './test-reports/' TEST_OUTPUT_DIR = "./test-reports/"
TEST_OUTPUT_VERBOSE = 1 TEST_OUTPUT_VERBOSE = 1
# new default in Django 3.0, making it explicit to facilitate bug hunting # new default in Django 3.0, making it explicit to facilitate bug hunting
X_FRAME_OPTIONS = 'DENY' X_FRAME_OPTIONS = "DENY"
SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_CONTENT_TYPE_NOSNIFF = True
# Django 3.2 uses BitAutoField by default, we keep things the old way # Django 3.2 uses BitAutoField by default, we keep things the old way
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -1,3 +1,4 @@
from django.http.request import HttpRequest
import requests import requests
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -5,10 +6,34 @@ from django.http.response import HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from graphene_django.views import GraphQLView from graphene_django.views import GraphQLView
from sentry_sdk.api import start_transaction
from wagtail.admin.views.pages.listing import index as wagtailadmin_explore from wagtail.admin.views.pages.listing import index as wagtailadmin_explore
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView): # For sentry perfomance monitoring
# taken from https://jerrynsh.com/how-to-monitor-python-graphql-api-with-sentry/
class SentryGraphQLView(GraphQLView):
def execute_graphql_request(
self,
request: HttpRequest,
data,
query,
variables,
operation_name,
show_graphiql,
):
operation_type = (
self.get_backend(request)
.document_from_string(self.schema, query)
.get_operation_type(operation_name)
)
with start_transaction(op=operation_type, name=operation_name):
return super().execute_graphql_request(
request, data, query, variables, operation_name, show_graphiql
)
class PrivateGraphQLView(LoginRequiredMixin, SentryGraphQLView):
pass pass
@ -16,22 +41,24 @@ class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
def home(request): def home(request):
if settings.SERVE_VIA_WEBPACK: if settings.SERVE_VIA_WEBPACK:
try: try:
res = requests.get('http://localhost:8080{}'.format(request.get_full_path())) res = requests.get(
"http://localhost:8080{}".format(request.get_full_path())
)
headers = res.headers headers = res.headers
content_type = headers.get('content-type', 'text/html') content_type = headers.get("content-type", "text/html")
return HttpResponse(res.text, content_type=content_type) return HttpResponse(res.text, content_type=content_type)
except Exception as e: except Exception as e:
print('Can not connect to dev server at http://localhost:8080:', e) print("Can not connect to dev server at http://localhost:8080:", e)
return render(request, 'index.html', {}) return render(request, "index.html", {})
def override_wagtailadmin_explore_default_ordering(request, parent_page_id): def override_wagtailadmin_explore_default_ordering(request, parent_page_id):
""" """
Wrap Wagtail's explore view to change the default ordering Wrap Wagtail's explore view to change the default ordering
""" """
if request.method == 'GET' and 'ordering' not in request.GET: if request.method == "GET" and "ordering" not in request.GET:
# Display reordering handles by default for children of all Page types. # Display reordering handles by default for children of all Page types.
return HttpResponseRedirect(request.path_info + '?ordering=ord') return HttpResponseRedirect(request.path_info + "?ordering=ord")
return wagtailadmin_explore(request, parent_page_id=parent_page_id) return wagtailadmin_explore(request, parent_page_id=parent_page_id)