Skip to content

Instantly share code, notes, and snippets.

@marksweb
Created October 4, 2023 20:32
Show Gist options
  • Save marksweb/c46f105dfa3252420b72df6eda38fb7b to your computer and use it in GitHub Desktop.
Save marksweb/c46f105dfa3252420b72df6eda38fb7b to your computer and use it in GitHub Desktop.
Single settings example
import os
import re
import sys
from django.db.models import options
import sentry_sdk
from base.utils.sanitize import sanitize
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
def gettext(s):
"""
i18n passthrough
"""
return s
env = os.environ.get
# Build paths inside the project like this: BASE_DIR / 'subdir'.
PROJECT_DIR = os.path.dirname(__file__)
BASE_DIR = os.path.abspath(os.path.join(PROJECT_DIR, '../'))
BUILD_ENV = sanitize(env('BUILD_ENV', False), bool)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
SECRET_KEY = env('SECRET_KEY', "")
ALLOWED_HOSTS = ['*']
ENV_ALLOWED_HOSTS = env('ALLOWED_HOSTS')
if ENV_ALLOWED_HOSTS:
ALLOWED_HOSTS.extend(ENV_ALLOWED_HOSTS.split(','))
SILENCED_SYSTEM_CHECKS = [
"admin.E039", # Check ensuring models are registered - broken by proxies
"debug_toolbar.W006", # Debug toolbar warns us for something we don't use
]
IGNORABLE_404_URLS = [
re.compile(r'^/favicon.ico')
]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
PERMISSIONS_POLICY = {
'autoplay': 'self',
'camera': 'none',
'geolocation': 'self',
'microphone': 'none',
"payment": ["*", ],
}
SECURE_REFERRER_POLICY = env('SECURE_REFERRER_POLICY', 'strict-origin')
X_FRAME_OPTIONS = env('X_FRAME_OPTIONS', 'SAMEORIGIN')
CSRF_COOKIE_SECURE = sanitize(env('CSRF_COOKIE_SECURE', False), bool)
SESSION_COOKIE_SECURE = sanitize(env('SESSION_COOKIE_SECURE', False), bool)
SECURE_BROWSER_XSS_FILTER = sanitize(env('SECURE_BROWSER_XSS_FILTER', False), bool)
SECURE_CONTENT_TYPE_NOSNIFF = sanitize(env('SECURE_CONTENT_TYPE_NOSNIFF', False), bool)
SECURE_HSTS_SECONDS = sanitize(env('SECURE_HSTS_SECONDS', 31536000), int)
SECURE_HSTS_PRELOAD = sanitize(env('SECURE_HSTS_PRELOAD', False), bool)
SECURE_HSTS_INCLUDE_SUBDOMAINS = sanitize(env('SECURE_HSTS_INCLUDE_SUBDOMAINS', False), bool)
# ***** PROJECT BASICS *****
PROJECT_NAME = 'project'
RELEASE_NUMBER = __import__('project').VERSION
VERSION = RELEASE_NUMBER
SITE_ID = 1
DEBUG = env('DEBUG', 'no') == 'yes'
WSGI_APPLICATION = 'project.wsgi.application'
ROOT_URLCONF = 'project.urls'
LOAD_TESTING = False
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = (
('en', gettext('English')),
)
FORMAT_MODULE_PATH = [
'project.locale',
]
# Application definition
INSTALLED_APPS = [
# custom admin theme
'admin_interface',
'colorfield',
# Django
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.flatpages',
'django.contrib.humanize',
'django.contrib.messages',
'django.contrib.redirects',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',
# 2FA
'django_otp',
'django_otp.plugins.otp_totp',
'allauth',
'allauth.account',
'allauth.socialaccount',
'anymail',
'auditlog',
'captcha',
'django_bleach',
'django_extensions',
'easy_thumbnails',
'explorer',
'factory_generator',
'filer',
'import_export',
'import_export_celery',
'storages',
'parler',
'cms',
'djangocms_ads',
'djangocms_alias',
'djangocms_attributes_field',
'djangocms_frontend',
'djangocms_frontend.contrib.accordion',
'djangocms_frontend.contrib.alert',
'djangocms_frontend.contrib.badge',
'djangocms_frontend.contrib.card',
'djangocms_frontend.contrib.collapse',
'djangocms_frontend.contrib.content',
'djangocms_frontend.contrib.grid',
'djangocms_frontend.contrib.image',
'djangocms_frontend.contrib.jumbotron',
'djangocms_frontend.contrib.link',
'djangocms_frontend.contrib.listgroup',
'djangocms_frontend.contrib.media',
'djangocms_frontend.contrib.navigation',
'djangocms_frontend.contrib.tabs',
'djangocms_frontend.contrib.utilities',
'djangocms_icon',
'djangocms_text_ckeditor',
'djangocms_versioning',
'menus',
'sekizai',
'treebeard',
'project',
'theme',
]
if DEBUG:
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django_permissions_policy.PermissionsPolicyMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
'allauth.account.middleware.AccountMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
'project.middleware.CachedFlatpageFallbackMiddleware',
'project.middleware.OTPMiddleware',
'project.middleware.StackOverflowMiddleware',
'location.middleware.country.CountryMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
'author.middlewares.AuthorDefaultBackendMiddleware',
'auditlog.middleware.AuditlogMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'project/templates'),
],
'APP_DIRS': False,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'base.contextprocessors.site',
'base.contextprocessors.utcnow',
'base.contextprocessors.release_number',
'base.contextprocessors.sentry_config',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
],
'loaders': [
'apptemplates.Loader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
},
},
]
CACHED_TEMPLATES = sanitize(env('CACHED_TEMPLATES', 'no'), bool)
if CACHED_TEMPLATES:
TEMPLATES[0]['OPTIONS']['loaders'] = [
(
'django.template.loaders.cached.Loader',
TEMPLATES[0]['OPTIONS']['loaders'],
),
]
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
if BUILD_ENV:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'project.db'),
}
}
else:
DATABASE_ENGINE = env(
'DATABASE_ENGINE',
'django.db.backends.postgresql'
)
DATABASE_HOST = env('DATABASE_HOST')
DATABASE_PORT = env('DATABASE_PORT')
DATABASE_USER = env('DATABASE_USER')
DATABASE_PASSWORD = env('DATABASE_PASSWORD')
DATABASE_NAME = env('DATABASE_NAME')
DATABASES = {
'default': {
'ENGINE': DATABASE_ENGINE,
'HOST': DATABASE_HOST,
'PORT': DATABASE_PORT,
'USER': DATABASE_USER,
'PASSWORD': DATABASE_PASSWORD,
'NAME': DATABASE_NAME,
'CONN_MAX_AGE': 0,
'ATOMIC_REQUESTS': False,
'TEST': {'SERIALIZE': False},
}
}
DATABASE_SSL = sanitize(env('DATABASE_SSL', 'no'), bool)
if 'test' in sys.argv:
DATABASES['default']['NAME'] = 'project'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# ***** CACHE *****
def setup_redis_location(rtype, db_default):
"""
Helper method to setup the redis server location based on the environment.
:param rtype: The redis cache type to look for. This is used to locate
environment variables in the form of
REDIS_<rtype>_HOST/SSL/etc..
:param db_default: The default redis database to use for this cache.
"""
rtype = rtype.upper()
redis_host = env(
f'REDIS_{rtype}_HOST',
env('REDIS_HOST', 'localhost'),
)
redis_ssl = env(
f'REDIS_{rtype}_SSL',
env('REDIS_SSL', 'no'),
)
redis_ssl = redis_ssl == 'yes'
redis_port = env(
f'REDIS_{rtype}_PORT',
env('REDIS_PORT', '6379' if not redis_ssl else '6380'),
)
redis_auth = env(
f'REDIS_{rtype}_AUTH',
env('REDIS_AUTH'),
)
redis_db = env(
f'REDIS_{rtype}_DB',
db_default,
)
if redis_ssl:
if redis_auth:
return f'rediss://:{redis_auth}@' \
f'{redis_host}:{redis_port}/{redis_db}'
return f'rediss://{redis_host}:{redis_port}/{redis_db}'
return f'redis://{redis_host}:{redis_port}/{redis_db}'
if BUILD_ENV:
CACHES = {
'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
'session': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
'pages': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}
}
else:
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': setup_redis_location(rtype='CACHE', db_default='0'),
},
'session': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': setup_redis_location(rtype='SESSION', db_default='1'),
},
'pages': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': setup_redis_location(rtype='PAGES', db_default='3'),
}
}
if DEBUG:
DEBUG_USE_CACHE = env('DEBUG_USE_CACHE', 'no') == 'yes'
if not DEBUG_USE_CACHE:
CACHES['default'] = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
CACHES['pages'] = {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
# ***** CELERY *****
ENABLE_CELERY = sanitize(env('ENABLE_CELERY', 'yes'), bool)
CELERY_BROKER_TYPE = env('CELERY_BROKER_TYPE', 'redis')
if CELERY_BROKER_TYPE == 'redis':
CELERY_BROKER_URL = setup_redis_location('CELERY', '2')
CELERY_BROKER_TRANSPORT_OPTIONS = {
'fanout_prefix': True,
'fanout_patterns': True,
'visibility_timeout': 600,
}
elif CELERY_BROKER_TYPE == 'sqs':
CELERY_BROKER_URL = 'sqs://'
CELERY_BROKER_TRANSPORT_OPTIONS = {
'region': 'eu-west-2',
'queue_name_prefix': env('CELERY_SQS_QUEUE_PREFIX') + '-',
}
else:
# undefined broker type, disable celery since we can't configure itr
ENABLE_CELERY = False
CELERY_ALWAYS_EAGER = not ENABLE_CELERY
CELERY_TASK_ALWAYS_EAGER = CELERY_ALWAYS_EAGER
CELERY_SEND_TASK_ERROR_EMAILS = True
CELERY_DEFAULT_QUEUE = 'celery' # changing this doesn't actually change the default
CELERY_QUEUES = {
'celery': {
'exchange': 'celery',
'binding_key': 'celery',
},
'high_priority': {
'exchange': 'high_priority',
'binding_key': 'high_priority',
},
'long_running': {
'exchange': 'long_running',
'binding_key': 'long_running',
},
}
CELERY_TIMEZONE = 'UTC'
# ***** AUTH *****
SESSION_COOKIE_AGE = sanitize(env('SESSION_COOKIE_AGE', 14400), int) # 4 hr
SESSION_CACHE_ALIAS = 'session'
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.' + validator
}
for validator in (
'UserAttributeSimilarityValidator',
'MinimumLengthValidator',
'CommonPasswordValidator',
'NumericPasswordValidator',
)
]
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
# ***** AUTH (ALLAUTH) *****
ACCOUNT_FORMS = {
'change_password': 'apps.accounts.forms.ChangePasswordForm',
'reset_password': 'apps.accounts.forms.ResetPasswordForm',
'signup': 'apps.accounts.forms.SignupForm',
}
ACCOUNT_ADAPTER = 'apps.accounts.adapter.CommsAdapter'
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_VERIFICATION = env('ACCOUNT_EMAIL_VERIFICATION', 'mandatory')
ACCOUNT_DEFAULT_HTTP_PROTOCOL = env('DEFAULT_HTTP_PROTOCOL', 'https')
SOCIALACCOUNT_PROVIDERS = {}
# ***** STATIC FILES *****
STATIC_URL = env('STATIC_URL', '/static/') # allow CloudFront override
LOCAL_STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static-collection')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'project', 'static'),
)
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
if 'test' not in sys.argv:
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# ***** MEDIA FILES / UPLOADS *****
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
if env('AWS_PROFILE'):
AWS_S3_SESSION_PROFILE = env('AWS_PROFILE')
S3_BUCKET = env('S3_BUCKET', 'trg-dev-test')
S3_SUB_BUCKET = env('S3_SUB_BUCKET', 'consoles')
S3_REGION = env('S3_REGION', 'eu-west-2')
AWS_REGION = env('AWS_REGION', 'eu-west-2')
S3_USE_SIGV4 = True
AWS_S3_BUCKET_URL = f'https://{AWS_S3_HOST}'
AWS_S3_SUB_BUCKET_URL = f'{AWS_S3_BUCKET_URL}/{AWS_S3_SUB_BUCKET}'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = AWS_S3_BUCKET_URL + '/'
# ***** EMAIL *****
EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend"
# ***** SENTRY *****
SENTRY_ENABLED = env('SENTRY_ENABLED', "no") == "yes"
SENTRY_DEBUG = env('SENTRY_DEBUG', "no") == "yes"
SENTRY_ENVIRONMENT = env('SENTRY_ENVIRONMENT', 'development')
SENTRY_PROTOCOL = env('SENTRY_PROTOCOL', 'https')
SENTRY_KEY = env('SENTRY_KEY', "")
SENTRY_HOST = env('SENTRY_HOST', "")
SENTRY_PROJECT = env('SENTRY_PROJECT', "")
SENTRY_DSN = f'{SENTRY_PROTOCOL}://{SENTRY_KEY}@{SENTRY_HOST}/{SENTRY_PROJECT}'
SENTRY_VERSION_PREFIX = env('SENTRY_VERSION_PREFIX', 'consoles')
if SENTRY_ENABLED:
sentry_sdk.init(
dsn=SENTRY_DSN,
environment=SENTRY_ENVIRONMENT,
integrations=[
CeleryIntegration(),
DjangoIntegration(),
RedisIntegration()
],
release=f"{SENTRY_VERSION_PREFIX}@{VERSION}",
traces_sample_rate=0.25,
_experiments={
"profiles_sample_rate": 0.25,
},
debug=SENTRY_DEBUG,
send_default_pii=True
)
try:
REVISION_HASH_FULL = \
open(os.path.join(BASE_DIR, 'revision.txt')).read().strip()
REVISION_HASH = REVISION_HASH_FULL[:8]
except OSError:
REVISION_HASH_FULL = None
REVISION_HASH = None
# ***** SQL EXPLORER *****
def check_sql_explorer_permission(request, *args, **kwargs):
"""
Check to see if the specified user is allowed to use SQL explorer
:param request: an HttpRequest
:type request: HttpRequest
:return: True if the user is allowed to use SQL explorer, False otherwise
:rtype: bool
"""
if not request:
return False
if request.user.is_anonymous:
return False
if not request.user.has_perm('explorer.change_query'):
return False
from django.contrib import admin
if not admin.site.has_permission(request):
return False
return True
EXPLORER_CONNECTIONS = {
'Default': 'default'
}
EXPLORER_DEFAULT_CONNECTION = 'default'
EXPLORER_PERMISSION_VIEW = check_sql_explorer_permission
EXPLORER_PERMISSION_CHANGE = check_sql_explorer_permission
# ***** AUTH (2FA) ****
OTP_TOTP_ISSUER = PROJECT_NAME
TWO_FACTOR_AUTH_ENFORCED = sanitize(env('TWO_FACTOR_AUTH_ENFORCED', 'no'), bool)
# ***** DEBUG TOOLBAR *****
DEBUG_TOOLBAR = DEBUG and env('DEBUG_TOOLBAR', 'no') == 'yes'
if DEBUG_TOOLBAR:
INTERNAL_IPS = [
'127.0.0.1',
]
INSTALLED_APPS += [
'debug_toolbar',
]
MIDDLEWARE += [
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
DEBUG_TOOLBAR_PATCH_SETTINGS = False
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': lambda request: not request.headers.get('X-Requested-With') == 'XMLHttpRequest',
}
# ***** BLEACH *****
BLEACH_ALLOWED_TAGS = [
'p', 'b', 'i', 'u', 'em', 'strong', 'a', 'img', 'span', 'div', 'style',
'table', 'thead', 'tbody', 'th', 'tr', 'td', 'ul', 'ol', 'li',
'blockquote', 'hr', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
]
BLEACH_ALLOWED_ATTRIBUTES = [
'href', 'title', 'style', 'src', 'alt', 'colspan', 'rowspan', 'width',
'height', 'target'
]
BLEACH_ALLOWED_STYLES = [
'color', 'font-family', 'font-weight', 'text-decoration', 'font-variant',
'text-align', 'font-size',
]
BLEACH_STRIP_TAGS = sanitize(env('BLEACH_STRIP_TAGS', True), bool)
BLEACH_STRIP_COMMENTS = sanitize(env('BLEACH_STRIP_COMMENTS', False), bool)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '[{levelname}] {asctime} {module} {process:d} {thread:d}: {message}',
'style': '{',
},
'simple': {
'format': '[{levelname}] {module}: {message}',
'style': '{',
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
# 'filters': ['require_debug_true'],
'formatter': 'simple'
},
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
'level': env('DJANGO_LOG_LEVEL', 'INFO')
},
'django.utils.autoreload': {
'level': 'INFO',
},
'apps': {
'handlers': ['console'],
'propagate': True,
'level': env('DJANGO_LOG_LEVEL', 'DEBUG')
},
'project': {
'handlers': ['console'],
'propagate': True,
'level': env('DJANGO_LOG_LEVEL', 'DEBUG')
},
},
}
THUMBNAIL_PROCESSORS = (
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
)
FILER_CANONICAL_URL = 'share/'
# ***** CMS *****
CMS_PAGE_CACHE = True
CMS_PLUGIN_CACHE = True
CMS_PERMISSION = False
CMS_TOOLBAR_ANONYMOUS_ON = False
def cms_language(lang):
"""
Construct the CMS language data structure for the specific language and
fallbacks (defaulting to English)
Allows shorthand for the many, many, per-site settings below
"""
languages = dict(LANGUAGES)
assert lang in languages, f'Invalid CMS language code: {lang}'
english = {
'code': 'en',
'name': languages['en']
}
if lang == 'en':
return [english]
return [
{
'code': lang,
'name': languages[lang],
'fallbacks': ['en'],
'redirect_on_fallback': False,
},
english
]
CMS_LANGUAGES = {
1: cms_language('en'), # base site
'default': {
'fallbacks': ['en'],
'redirect_on_fallback': True,
'public': True,
'hide_untranslated': False,
},
}
DJANGOCMS_FRONTEND_THEME = 'theme'
def around_template(name):
"""Format the name of a template for djangocms-frontend around"""
return f"theme/around/{name}"
CMS_PAGE_TEMPLATE = 'cms/page.html' # Used in the automated setup of pages
CMS_TEMPLATES = (
# Keep this alphabetical and it's easy to use!
# (CMS_PAGE_TEMPLATE, 'Page'),
(around_template('page.html'), 'Plain page (no navbar)'),
(around_template('page_with_nav.html'), 'Plain page (with navbar)'),
(around_template('experiences_with_nav.html'), 'Experiences page (with navbar)'),
(around_template('colour_strip/page.html'), 'Page with colour strip (no navbar)'),
(around_template('colour_strip/page_with_nav.html'), 'Page with colour strip (with navbar)'),
)
CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED = True
CMS_CONFIRM_VERSION4 = True
DJANGOCMS_VERSIONING_USERNAME_FIELD = 'email'
DJANGOCMS_VERSIONING_LOCK_VERSIONS = True
# MAILCHIMP CREDENTIALS
MAILCHIMP_API_KEY = env('MAILCHIMP_API_KEY')
MAILCHIMP_DATA_CENTER = env('MAILCHIMP_DATA_CENTER')
MAILCHIMP_EMAIL_LIST_ID = env('MAILCHIMP_EMAIL_LIST_ID')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment