diff --git a/deploy-saleor.sh b/deploy-saleor.sh index 35671bd..ce84b6b 100644 --- a/deploy-saleor.sh +++ b/deploy-saleor.sh @@ -258,18 +258,18 @@ done echo -n "Enter a custom Static Files URI (optional):" read STATIC_URL # Get the Admin's email address -while [ "$EMAIL" = "" ] +while [ "$ADMIN_EMAIL" = "" ] do echo "" echo -n "Enter the Dashboard admin's email:" - read EMAIL + read ADMIN_EMAIL done # Get the Admin's desired password -while [ "$PASSW" = "" ] +while [ "$ADMIN_PASS" = "" ] do echo "" echo -n "Enter the Dashboard admin's desired password:" - read -s PASSW + read -s ADMIN_PASS done ######################################################################################### @@ -376,24 +376,28 @@ echo "" # Replace any parameter slugs in the template files with real paramaters & write them to # the production files ######################################################################################### +# Replace the settings.py with the production version +if [ -f "$HD/saleor/saleor/settings.py" ]; then + sudo rm $HD/saleor/saleor/settings.py +fi +sudo cp $HD/Deploy_Saleor/resources/saleor/settings.py $HD/saleor/saleor/ +# Replace the populatedb.py file with the production version +if [ -f "$HD/saleor/saleor/core/management/commands/populatedb.py" ]; then + sudo rm $HD/saleor/saleor/core/management/commands/populatedb.py +fi +sudo cp $HD/Deploy_Saleor/resources/saleor/populatedb.py $HD/saleor/saleor/core/management/commands/ +# Replace the test_core.py file with the production version +if [ -f "$HD/saleor/saleor/core/tests/test_core.py" ]; then + sudo rm $HD/saleor/saleor/core/tests/test_core.py +fi +sudo cp $HD/Deploy_Saleor/resources/saleor/test_core.py $HD/saleor/saleor/core/tests/ +wait # Does an old saleor.service file exist? if [ -f "/etc/systemd/system/saleor.service" ]; then # Remove the old service file sudo rm /etc/systemd/system/saleor.service fi -READ_ENV=' -try: - from decouple import RepositoryEnv - file_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) - #print("{}/.env".format(file_path)) - for k,v in RepositoryEnv("{}/.env".format(file_path)).data.items(): - print(k, v) - os.environ[k] = v -except Exception as e: - #print(e) - pass - -def get_list(text):' +###### This following section is for future use and will be modified to allow an alternative repo clone ###### # Was the -v (version) option used or Mirumee repo specified? if [ "vOPT" = "true" ] || [ "$REPO" = "mirumee" ]; then # Create the new service file @@ -411,16 +415,6 @@ if [ "vOPT" = "true" ] || [ "$REPO" = "mirumee" ]; then s/{host}/$HOST/g s/{apiport}/$API_PORT/" $HD/Deploy_Saleor/resources/saleor/server_block > /etc/nginx/sites-available/saleor wait - # Replace demo credentials with production credentials in /saleor/saleor/core/management/commands/populatedb.py - sudo sed -i 's/{"email": "admin@example.com", "password": "admin"}/{"email": "'$EMAIL'", "password": "'$PASSW'"}/' $HD/saleor/saleor/core/management/commands/populatedb.py - wait - # Replace demo credentials with production credentials in /saleor/saleor/core/tests/test_core.py - sudo sed -i 's/{"email": "admin@example.com", "password": "admin"}/{"email": "'$EMAIL'", "password": "'$PASSW'"}/' $HD/saleor/saleor/core/tests/test_core.py - wait - # Replace the insecure demo secret key assignemnt with a more secure file reference in /saleor/saleor/settings.py - sudo sed -i 's|SECRET_KEY = os.environ.get("SECRET_KEY")|with open("/etc/saleor/api_sk") as f: SECRET_KEY = f.read().strip()| - s|def get_list(text):|'$READ_ENV'|' $HD/saleor/saleor/settings.py - wait else # Create the new service file sudo sed "s/{un}/$UN/ @@ -437,42 +431,6 @@ else s/{host}/$HOST/g s/{apiport}/$API_PORT/" $HD/Deploy_Saleor/resources/saleor/server_block > /etc/nginx/sites-available/saleor wait - # Replace demo credentials with production credentials in /saleor/saleor/core/management/commands/populatedb.py - sudo sed -i "s/{\"email\": \"admin@example.com\", \"password\": \"admin\"}/{\"email\": \"$EMAIL\", \"password\": \"$PASSW\"}/" $HD/saleor/saleor/core/management/commands/populatedb.py - wait - # Replace demo credentials with production credentials in /saleor/saleor/core/tests/test_core.py - sudo sed -i "s/{\"email\": \"admin@example.com\", \"password\": \"admin\"}/{\"email\": \"$EMAIL\", \"password\": \"$PASSW\"}/" $HD/saleor/saleor/core/tests/test_core.py - wait - # Replace the insecure demo secret key assignemnt with a more secure file reference in /saleor/saleor/settings.py - sudo sed -i "s|SECRET_KEY = os.environ.get(\"SECRET_KEY\")|with open('/etc/saleor/api_sk') as f: SECRET_KEY = f.read().strip()| - s|def get_list(text):|$READ_ENV|" $HD/saleor/saleor/settings.py - wait - - ###### For possible later use ###### - # Create the new service file - #sudo sed "s/{un}/$UN/ - # s|{hd}|$HD|" $HD/saleor/resources/saleor/template.service > /etc/systemd/system/saleor.service - #wait - # Does an old server block exist? - #if [ -f "/etc/nginx/sites-available/saleor" ]; then - # # Remove the old service file - # sudo rm /etc/nginx/sites-available/saleor - #fi - # Create the new server block - #sudo sed "s|{hd}|$HD| - # s/{api_host}/$API_HOST/ - # s/{host}/$HOST/g - # s/{apiport}/$API_PORT/" $HD/saleor/resources/saleor/server_block > /etc/nginx/sites-available/saleor - #wait - # Set the production credentials in /saleor/saleor/core/management/commands/populatedb.py - #sudo sed -i "s/{email}/$EMAIL/ - # s/{passw}/$PASSW/" $HD/saleor/saleor/core/management/commands/populatedb.py - #wait - # Set the production credentials in /saleor/saleor/core/tests/test_core.py - #sudo sed -i "s/{email}/$EMAIL/ - # s/{passw}/$PASSW/" $HD/saleor/saleor/core/tests/test_core.py - #wait - ###### For possible later use ###### fi ######################################################################################### @@ -503,6 +461,7 @@ sudo sed "s|{dburl}|$DB_URL| s/{ahosts}/$A_HOSTS/ s|{static}|$STATIC_URL| s|{media}|$MEDIA_URL| + s/{adminemail}/$ADMIN_EMAIL/ s/{gqlorigins}/$QL_ORIGINS/" $HD/Deploy_Saleor/resources/saleor/template.env > $HD/saleor/.env wait ######################################################################################### @@ -561,18 +520,29 @@ wait # Install the project requirements pip3 install -r requirements.txt wait +# Install the decoupler for .env file +pip3 install python3-decouple +wait +# Set any secret Environment Variables +export ADMIN_PASS="$ADMIN_PASS" # Install the project npm install +wait # Run an audit to fix any vulnerabilities npm audit fix +wait # Establish the database -python3 manage.py migrate +python3 manage.py migrate --createsuperuser +wait # Collect the static elemants python3 manage.py collectstatic +wait # Build the schema npm run build-schema +wait # Build the emails npm run build-emails +wait # Exit the virtual environment here? _#_ deactivate ######################################################################################### @@ -580,17 +550,13 @@ deactivate ######################################################################################### -# Create the Saleor service +# Enable the Saleor service ######################################################################################### -# Touch -sudo touch /etc/init.d/saleor -# Allow execute -sudo chmod +x /etc/init.d/saleor # Update with defaults -sudo update-rc.d saleor defaults +sudo systemctl enable saleor.service ######################################################################################### - - +sudo systemctl daemon-reload +systemctl start emperor.uwsgi.service ######################################################################################### echo "Enabling server block and Restarting nginx..." @@ -606,4 +572,11 @@ sudo systemctl restart nginx echo "" echo "Finished creating production deployment packages for Saleor API & GraphQL" echo "" +######################################################################################### + + +######################################################################################### +# Call the dashboard deployment script +######################################################################################### +source ./deploy-dashboard.sh ######################################################################################### \ No newline at end of file diff --git a/resources/saleor/populatedb.py b/resources/saleor/populatedb.py new file mode 100644 index 0000000..2f29c12 --- /dev/null +++ b/resources/saleor/populatedb.py @@ -0,0 +1,142 @@ +from io import StringIO + +from django.apps import apps +from django.conf import settings +from django.core.management import call_command +from django.core.management.base import BaseCommand +from django.db import connection + +from ....account.utils import create_superuser +from ...utils.random_data import ( + add_address_to_admin, + create_channels, + create_gift_card, + create_menus, + create_orders, + create_page_type, + create_pages, + create_permission_groups, + create_product_sales, + create_products_by_schema, + create_shipping_zones, + create_staffs, + create_users, + create_vouchers, + create_warehouses, +) + + +class Command(BaseCommand): + help = "Populate database with test objects" + placeholders_dir = "saleor/static/placeholders/" + + def add_arguments(self, parser): + parser.add_argument( + "--createsuperuser", + action="store_true", + dest="createsuperuser", + default=False, + help="Create admin account", + ) + parser.add_argument( + "--sampledata", + action="store_true", + dest="sampledata", + default=False, + help="Create sample products, etc..", + ) + parser.add_argument( + "--withoutimages", + action="store_true", + dest="withoutimages", + default=False, + help="Don't create product images", + ) + parser.add_argument( + "--skipsequencereset", + action="store_true", + dest="skipsequencereset", + default=False, + help="Don't reset SQL sequences that are out of sync.", + ) + + def make_database_faster(self): + """Sacrifice some of the safeguards of sqlite3 for speed. + + Users are not likely to run this command in a production environment. + They are even less likely to run it in production while using sqlite3. + """ + if "sqlite3" in connection.settings_dict["ENGINE"]: + cursor = connection.cursor() + cursor.execute("PRAGMA temp_store = MEMORY;") + cursor.execute("PRAGMA synchronous = OFF;") + + def sequence_reset(self): + """Run a SQL sequence reset on all saleor.* apps. + + When a value is manually assigned to an auto-incrementing field + it doesn't update the field's sequence, which might cause a conflict + later on. + """ + commands = StringIO() + for app in apps.get_app_configs(): + if "saleor" in app.name: + call_command( + "sqlsequencereset", app.label, stdout=commands, no_color=True + ) + with connection.cursor() as cursor: + cursor.execute(commands.getvalue()) + + def handle(self, *args, **options): + # set only our custom plugin to not call external API when preparing + # example database + settings.PLUGINS = [ + "saleor.payment.gateways.dummy.plugin.DummyGatewayPlugin", + "saleor.payment.gateways.dummy_credit_card.plugin." + "DummyCreditCardGatewayPlugin", + ] + + ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL") + ADMIN_PASS = os.environ.get("ADMIN_PASS") + + if options["createsuperuser"]: + credentials = {"email": ADMIN_EMAIL, "password": ADMIN_PASS} + msg = create_superuser(credentials) + self.stdout.write(msg) + add_address_to_admin(credentials["email"]) + + if options["sampledata"]: + self.make_database_faster() + create_images = not options["withoutimages"] + for msg in create_channels(): + self.stdout.write(msg) + for msg in create_shipping_zones(): + self.stdout.write(msg) + create_warehouses() + self.stdout.write("Created warehouses") + for msg in create_page_type(): + self.stdout.write(msg) + for msg in create_pages(): + self.stdout.write(msg) + create_products_by_schema(self.placeholders_dir, create_images) + self.stdout.write("Created products") + for msg in create_product_sales(5): + self.stdout.write(msg) + for msg in create_vouchers(): + self.stdout.write(msg) + for msg in create_gift_card(): + self.stdout.write(msg) + for msg in create_users(20): + self.stdout.write(msg) + for msg in create_orders(20): + self.stdout.write(msg) + for msg in create_menus(): + self.stdout.write(msg) + + if not options["skipsequencereset"]: + self.sequence_reset() + + for msg in create_permission_groups(): + self.stdout.write(msg) + for msg in create_staffs(): + self.stdout.write(msg) diff --git a/resources/saleor/settings.py b/resources/saleor/settings.py new file mode 100644 index 0000000..208f368 --- /dev/null +++ b/resources/saleor/settings.py @@ -0,0 +1,603 @@ +import ast +import os.path +import warnings +from datetime import timedelta + +import dj_database_url +import dj_email_url +import django_cache_url +import jaeger_client +import jaeger_client.config +import pkg_resources +import sentry_sdk +import sentry_sdk.utils +from django.core.exceptions import ImproperlyConfigured +from django.core.management.utils import get_random_secret_key +from pytimeparse import parse +from sentry_sdk.integrations.celery import CeleryIntegration +from sentry_sdk.integrations.django import DjangoIntegration + + +try: + from decouple import RepositoryEnv + file_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) + #print("{}/.env".format(file_path)) + for k,v in RepositoryEnv("{}/.env".format(file_path)).data.items(): + print(k, v) + os.environ[k] = v +except Exception as e: + #print(e) + pass + + +def get_list(text): + return [item.strip() for item in text.split(",")] + + +def get_bool_from_env(name, default_value): + if name in os.environ: + value = os.environ[name] + try: + return ast.literal_eval(value) + except ValueError as e: + raise ValueError("{} is an invalid value for {}".format(value, name)) from e + return default_value + + +DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False' + +SITE_ID = 1 + +PROJECT_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) + +ROOT_URLCONF = "saleor.urls" + +WSGI_APPLICATION = "saleor.wsgi.application" + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) +MANAGERS = ADMINS + +_DEFAULT_CLIENT_HOSTS = "localhost,127.0.0.1" + +ALLOWED_CLIENT_HOSTS = os.environ.get("ALLOWED_CLIENT_HOSTS") +if not ALLOWED_CLIENT_HOSTS: + if DEBUG: + ALLOWED_CLIENT_HOSTS = _DEFAULT_CLIENT_HOSTS + else: + raise ImproperlyConfigured( + "ALLOWED_CLIENT_HOSTS environment variable must be set when DEBUG=False." + ) + +ALLOWED_CLIENT_HOSTS = get_list(ALLOWED_CLIENT_HOSTS) + +INTERNAL_IPS = get_list(os.environ.get("INTERNAL_IPS", "127.0.0.1")) + +DATABASES = { + "default": dj_database_url.config( + default="postgres://saleor:saleor@localhost:5432/saleor", conn_max_age=600 + ) +} + + +TIME_ZONE = "UTC" +LANGUAGE_CODE = "en" +LANGUAGES = [ + ("ar", "Arabic"), + ("az", "Azerbaijani"), + ("bg", "Bulgarian"), + ("bn", "Bengali"), + ("ca", "Catalan"), + ("cs", "Czech"), + ("da", "Danish"), + ("de", "German"), + ("el", "Greek"), + ("en", "English"), + ("es", "Spanish"), + ("es-co", "Colombian Spanish"), + ("et", "Estonian"), + ("fa", "Persian"), + ("fi", "Finnish"), + ("fr", "French"), + ("hi", "Hindi"), + ("hu", "Hungarian"), + ("hy", "Armenian"), + ("id", "Indonesian"), + ("is", "Icelandic"), + ("it", "Italian"), + ("ja", "Japanese"), + ("ka", "Georgian"), + ("km", "Khmer"), + ("ko", "Korean"), + ("lt", "Lithuanian"), + ("mn", "Mongolian"), + ("my", "Burmese"), + ("nb", "Norwegian"), + ("nl", "Dutch"), + ("pl", "Polish"), + ("pt", "Portuguese"), + ("pt-br", "Brazilian Portuguese"), + ("ro", "Romanian"), + ("ru", "Russian"), + ("sk", "Slovak"), + ("sl", "Slovenian"), + ("sq", "Albanian"), + ("sr", "Serbian"), + ("sv", "Swedish"), + ("sw", "Swahili"), + ("ta", "Tamil"), + ("th", "Thai"), + ("tr", "Turkish"), + ("uk", "Ukrainian"), + ("vi", "Vietnamese"), + ("zh-hans", "Simplified Chinese"), + ("zh-hant", "Traditional Chinese"), +] +LOCALE_PATHS = [os.path.join(PROJECT_ROOT, "locale")] +USE_I18N = True +USE_L10N = True +USE_TZ = True + +FORM_RENDERER = "django.forms.renderers.TemplatesSetting" + +EMAIL_URL = os.environ.get("EMAIL_URL") +SENDGRID_USERNAME = os.environ.get("SENDGRID_USERNAME") +SENDGRID_PASSWORD = os.environ.get("SENDGRID_PASSWORD") +if not EMAIL_URL and SENDGRID_USERNAME and SENDGRID_PASSWORD: + EMAIL_URL = "smtp://%s:%s@smtp.sendgrid.net:587/?tls=True" % ( + SENDGRID_USERNAME, + SENDGRID_PASSWORD, + ) +email_config = dj_email_url.parse( + EMAIL_URL or "console://demo@example.com:console@example/" +) + +EMAIL_FILE_PATH = email_config["EMAIL_FILE_PATH"] +EMAIL_HOST_USER = email_config["EMAIL_HOST_USER"] +EMAIL_HOST_PASSWORD = email_config["EMAIL_HOST_PASSWORD"] +EMAIL_HOST = email_config["EMAIL_HOST"] +EMAIL_PORT = email_config["EMAIL_PORT"] +EMAIL_BACKEND = email_config["EMAIL_BACKEND"] +EMAIL_USE_TLS = email_config["EMAIL_USE_TLS"] +EMAIL_USE_SSL = email_config["EMAIL_USE_SSL"] + +# If enabled, make sure you have set proper storefront address in ALLOWED_CLIENT_HOSTS. +ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL = get_bool_from_env( + "ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL", True +) + +ENABLE_SSL = get_bool_from_env("ENABLE_SSL", False) + +if ENABLE_SSL: + SECURE_SSL_REDIRECT = not DEBUG + +DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", EMAIL_HOST_USER) + +MEDIA_ROOT = os.path.join(PROJECT_ROOT, "media") +MEDIA_URL = os.environ.get("MEDIA_URL", "/media/") + +STATIC_ROOT = os.path.join(PROJECT_ROOT, "static") +STATIC_URL = os.environ.get("STATIC_URL", "/static/") +STATICFILES_DIRS = [ + ("images", os.path.join(PROJECT_ROOT, "saleor", "static", "images")) +] +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + +context_processors = [ + "django.template.context_processors.debug", + "django.template.context_processors.media", + "django.template.context_processors.static", + "saleor.site.context_processors.site", +] + +loaders = [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", +] + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(PROJECT_ROOT, "templates")], + "OPTIONS": { + "debug": DEBUG, + "context_processors": context_processors, + "loaders": loaders, + "string_if_invalid": '<< MISSING VARIABLE "%s" >>' if DEBUG else "", + }, + } +] + +# Read secret key from a file +with open('/etc/saleor/api_sk') as f: + SECRET_KEY = f.read().strip() + +if not SECRET_KEY and DEBUG: + warnings.warn("SECRET_KEY not configured, using a random temporary key.") + SECRET_KEY = get_random_secret_key() + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.middleware.common.CommonMiddleware", + "saleor.core.middleware.request_time", + "saleor.core.middleware.discounts", + "saleor.core.middleware.google_analytics", + "saleor.core.middleware.country", + "saleor.core.middleware.currency", + "saleor.core.middleware.site", + "saleor.core.middleware.plugins", + "saleor.core.middleware.jwt_refresh_token_middleware", +] + +INSTALLED_APPS = [ + # External apps that need to go before django's + "storages", + # Django modules + "django.contrib.contenttypes", + "django.contrib.sites", + "django.contrib.staticfiles", + "django.contrib.auth", + "django.contrib.postgres", + # Local apps + "saleor.plugins", + "saleor.account", + "saleor.discount", + "saleor.giftcard", + "saleor.product", + "saleor.attribute", + "saleor.channel", + "saleor.checkout", + "saleor.core", + "saleor.csv", + "saleor.graphql", + "saleor.menu", + "saleor.order", + "saleor.invoice", + "saleor.seo", + "saleor.shipping", + "saleor.search", + "saleor.site", + "saleor.page", + "saleor.payment", + "saleor.warehouse", + "saleor.webhook", + "saleor.wishlist", + "saleor.app", + # External apps + "versatileimagefield", + "django_measurement", + "django_prices", + "django_prices_openexchangerates", + "django_prices_vatlayer", + "graphene_django", + "mptt", + "django_countries", + "django_filters", + "phonenumber_field", +] + + +ENABLE_DEBUG_TOOLBAR = get_bool_from_env("ENABLE_DEBUG_TOOLBAR", False) +if ENABLE_DEBUG_TOOLBAR: + # Ensure the graphiql debug toolbar is actually installed before adding it + try: + __import__("graphiql_debug_toolbar") + except ImportError as exc: + msg = ( + f"{exc} -- Install the missing dependencies by " + f"running `pip install -r requirements_dev.txt`" + ) + warnings.warn(msg) + else: + INSTALLED_APPS += ["django.forms", "debug_toolbar", "graphiql_debug_toolbar"] + MIDDLEWARE.append("saleor.graphql.middleware.DebugToolbarMiddleware") + + DEBUG_TOOLBAR_PANELS = [ + "ddt_request_history.panels.request_history.RequestHistoryPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", + ] + DEBUG_TOOLBAR_CONFIG = {"RESULTS_CACHE_SIZE": 100} + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "root": {"level": "INFO", "handlers": ["default"]}, + "formatters": { + "django.server": { + "()": "django.utils.log.ServerFormatter", + "format": "[{server_time}] {message}", + "style": "{", + }, + "json": { + "()": "saleor.core.logging.JsonFormatter", + "datefmt": "%Y-%m-%dT%H:%M:%SZ", + "format": ( + "%(asctime)s %(levelname)s %(lineno)s %(message)s %(name)s " + + "%(pathname)s %(process)d %(threadName)s" + ), + }, + "verbose": { + "format": ( + "%(levelname)s %(name)s %(message)s [PID:%(process)d:%(threadName)s]" + ) + }, + }, + "handlers": { + "default": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose" if DEBUG else "json", + }, + "django.server": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "django.server" if DEBUG else "json", + }, + }, + "loggers": { + "django": {"level": "INFO", "propagate": True}, + "django.server": { + "handlers": ["django.server"], + "level": "INFO", + "propagate": False, + }, + "saleor": {"level": "DEBUG", "propagate": True}, + "saleor.graphql.errors.handled": { + "handlers": ["default"], + "level": "INFO", + "propagate": False, + }, + "graphql.execution.utils": {"propagate": False}, + }, +} + +AUTH_USER_MODEL = "account.User" + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": {"min_length": 8}, + } +] + +DEFAULT_COUNTRY = os.environ.get("DEFAULT_COUNTRY", "US") +DEFAULT_CURRENCY = os.environ.get("DEFAULT_CURRENCY", "USD") +DEFAULT_DECIMAL_PLACES = 3 +DEFAULT_MAX_DIGITS = 12 +DEFAULT_CURRENCY_CODE_LENGTH = 3 + +# The default max length for the display name of the +# sender email address. +# Following the recommendation of https://tools.ietf.org/html/rfc5322#section-2.1.1 +DEFAULT_MAX_EMAIL_DISPLAY_NAME_LENGTH = 78 + +COUNTRIES_OVERRIDE = {"EU": "European Union"} + +OPENEXCHANGERATES_API_KEY = os.environ.get("OPENEXCHANGERATES_API_KEY") + +GOOGLE_ANALYTICS_TRACKING_ID = os.environ.get("GOOGLE_ANALYTICS_TRACKING_ID") + + +def get_host(): + from django.contrib.sites.models import Site + + return Site.objects.get_current().domain + + +PAYMENT_HOST = get_host + +PAYMENT_MODEL = "order.Payment" + +MAX_CHECKOUT_LINE_QUANTITY = int(os.environ.get("MAX_CHECKOUT_LINE_QUANTITY", 50)) + +TEST_RUNNER = "saleor.tests.runner.PytestTestRunner" + + +PLAYGROUND_ENABLED = get_bool_from_env("PLAYGROUND_ENABLED", True) + +ALLOWED_HOSTS = get_list(os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1")) +ALLOWED_GRAPHQL_ORIGINS = get_list(os.environ.get("ALLOWED_GRAPHQL_ORIGINS", "*")) + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +# Amazon S3 configuration +# See https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html +AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID") +AWS_LOCATION = os.environ.get("AWS_LOCATION", "") +AWS_MEDIA_BUCKET_NAME = os.environ.get("AWS_MEDIA_BUCKET_NAME") +AWS_MEDIA_CUSTOM_DOMAIN = os.environ.get("AWS_MEDIA_CUSTOM_DOMAIN") +AWS_QUERYSTRING_AUTH = get_bool_from_env("AWS_QUERYSTRING_AUTH", False) +AWS_QUERYSTRING_EXPIRE = get_bool_from_env("AWS_QUERYSTRING_EXPIRE", 3600) +AWS_S3_CUSTOM_DOMAIN = os.environ.get("AWS_STATIC_CUSTOM_DOMAIN") +AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", None) +AWS_S3_REGION_NAME = os.environ.get("AWS_S3_REGION_NAME", None) +AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY") +AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME") +AWS_DEFAULT_ACL = os.environ.get("AWS_DEFAULT_ACL", None) + +# Google Cloud Storage configuration +GS_PROJECT_ID = os.environ.get("GS_PROJECT_ID") +GS_STORAGE_BUCKET_NAME = os.environ.get("GS_STORAGE_BUCKET_NAME") +GS_MEDIA_BUCKET_NAME = os.environ.get("GS_MEDIA_BUCKET_NAME") +GS_AUTO_CREATE_BUCKET = get_bool_from_env("GS_AUTO_CREATE_BUCKET", False) +GS_QUERYSTRING_AUTH = get_bool_from_env("GS_QUERYSTRING_AUTH", False) +GS_DEFAULT_ACL = os.environ.get("GS_DEFAULT_ACL", None) +GS_MEDIA_CUSTOM_ENDPOINT = os.environ.get("GS_MEDIA_CUSTOM_ENDPOINT", None) +GS_EXPIRATION = os.environ.get("GS_EXPIRATION", None) + +# If GOOGLE_APPLICATION_CREDENTIALS is set there is no need to load OAuth token +# See https://django-storages.readthedocs.io/en/latest/backends/gcloud.html +if "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ: + GS_CREDENTIALS = os.environ.get("GS_CREDENTIALS") + +if AWS_STORAGE_BUCKET_NAME: + STATICFILES_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" +elif GS_STORAGE_BUCKET_NAME: + STATICFILES_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" + +if AWS_MEDIA_BUCKET_NAME: + DEFAULT_FILE_STORAGE = "saleor.core.storages.S3MediaStorage" + THUMBNAIL_DEFAULT_STORAGE = DEFAULT_FILE_STORAGE +elif GS_MEDIA_BUCKET_NAME: + DEFAULT_FILE_STORAGE = "saleor.core.storages.GCSMediaStorage" + THUMBNAIL_DEFAULT_STORAGE = DEFAULT_FILE_STORAGE + +VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = { + "products": [ + ("product_gallery", "thumbnail__540x540"), + ("product_gallery_2x", "thumbnail__1080x1080"), + ("product_small", "thumbnail__60x60"), + ("product_small_2x", "thumbnail__120x120"), + ("product_list", "thumbnail__255x255"), + ("product_list_2x", "thumbnail__510x510"), + ], + "background_images": [("header_image", "thumbnail__1080x440")], + "user_avatars": [("default", "thumbnail__445x445")], +} + +VERSATILEIMAGEFIELD_SETTINGS = { + # Images should be pre-generated on Production environment + "create_images_on_demand": get_bool_from_env("CREATE_IMAGES_ON_DEMAND", DEBUG) +} + +PLACEHOLDER_IMAGES = { + 60: "images/placeholder60x60.png", + 120: "images/placeholder120x120.png", + 255: "images/placeholder255x255.png", + 540: "images/placeholder540x540.png", + 1080: "images/placeholder1080x1080.png", +} + +DEFAULT_PLACEHOLDER = "images/placeholder255x255.png" + +SEARCH_BACKEND = "saleor.search.backends.postgresql" + +AUTHENTICATION_BACKENDS = [ + "saleor.core.auth_backend.JSONWebTokenBackend", +] + +# CELERY SETTINGS +CELERY_TIMEZONE = TIME_ZONE +CELERY_BROKER_URL = ( + os.environ.get("CELERY_BROKER_URL", os.environ.get("CLOUDAMQP_URL")) or "" +) +CELERY_TASK_ALWAYS_EAGER = not CELERY_BROKER_URL +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" +CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", None) + +# Change this value if your application is running behind a proxy, +# e.g. HTTP_CF_Connecting_IP for Cloudflare or X_FORWARDED_FOR +REAL_IP_ENVIRON = os.environ.get("REAL_IP_ENVIRON", "REMOTE_ADDR") + +# Slugs for menus precreated in Django migrations +DEFAULT_MENUS = {"top_menu_name": "navbar", "bottom_menu_name": "footer"} + +# Slug for channel precreated in Django migrations +DEFAULT_CHANNEL_SLUG = os.environ.get("DEFAULT_CHANNEL_SLUG", "default-channel") + + +# Sentry +sentry_sdk.utils.MAX_STRING_LENGTH = 4096 +SENTRY_DSN = os.environ.get("SENTRY_DSN") +if SENTRY_DSN: + sentry_sdk.init( + dsn=SENTRY_DSN, integrations=[CeleryIntegration(), DjangoIntegration()] + ) + +GRAPHENE = { + "RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST": True, + "RELAY_CONNECTION_MAX_LIMIT": 100, + "MIDDLEWARE": [ + "saleor.graphql.middleware.OpentracingGrapheneMiddleware", + "saleor.graphql.middleware.JWTMiddleware", + "saleor.graphql.middleware.app_middleware", + ], +} + +PLUGINS_MANAGER = "saleor.plugins.manager.PluginsManager" + +PLUGINS = [ + "saleor.plugins.avatax.plugin.AvataxPlugin", + "saleor.plugins.vatlayer.plugin.VatlayerPlugin", + "saleor.plugins.webhook.plugin.WebhookPlugin", + "saleor.payment.gateways.dummy.plugin.DummyGatewayPlugin", + "saleor.payment.gateways.dummy_credit_card.plugin.DummyCreditCardGatewayPlugin", + "saleor.payment.gateways.stripe.plugin.StripeGatewayPlugin", + "saleor.payment.gateways.braintree.plugin.BraintreeGatewayPlugin", + "saleor.payment.gateways.razorpay.plugin.RazorpayGatewayPlugin", + "saleor.payment.gateways.adyen.plugin.AdyenGatewayPlugin", + "saleor.payment.gateways.authorize_net.plugin.AuthorizeNetGatewayPlugin", + "saleor.plugins.invoicing.plugin.InvoicingPlugin", +] + +# Plugin discovery +installed_plugins = pkg_resources.iter_entry_points("saleor.plugins") +for entry_point in installed_plugins: + plugin_path = "{}.{}".format(entry_point.module_name, entry_point.attrs[0]) + if plugin_path not in PLUGINS: + if entry_point.name not in INSTALLED_APPS: + INSTALLED_APPS.append(entry_point.name) + PLUGINS.append(plugin_path) + +if ( + not DEBUG + and ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL + and ALLOWED_CLIENT_HOSTS == get_list(_DEFAULT_CLIENT_HOSTS) +): + raise ImproperlyConfigured( + "Make sure you've added storefront address to ALLOWED_CLIENT_HOSTS " + "if ENABLE_ACCOUNT_CONFIRMATION_BY_EMAIL is enabled." + ) + +# Initialize a simple and basic Jaeger Tracing integration +# for open-tracing if enabled. +# +# Refer to our guide on https://docs.saleor.io/docs/next/guides/opentracing-jaeger/. +# +# If running locally, set: +# JAEGER_AGENT_HOST=localhost +if "JAEGER_AGENT_HOST" in os.environ: + jaeger_client.Config( + config={ + "sampler": {"type": "const", "param": 1}, + "local_agent": { + "reporting_port": os.environ.get( + "JAEGER_AGENT_PORT", jaeger_client.config.DEFAULT_REPORTING_PORT + ), + "reporting_host": os.environ.get("JAEGER_AGENT_HOST"), + }, + "logging": get_bool_from_env("JAEGER_LOGGING", False), + }, + service_name="saleor", + validate=True, + ).initialize_tracer() + + +# Some cloud providers (Heroku) export REDIS_URL variable instead of CACHE_URL +REDIS_URL = os.environ.get("REDIS_URL") +if REDIS_URL: + CACHE_URL = os.environ.setdefault("CACHE_URL", REDIS_URL) +CACHES = {"default": django_cache_url.config()} + +# Default False because storefront and dashboard don't support expiration of token +JWT_EXPIRE = get_bool_from_env("JWT_EXPIRE", False) +JWT_TTL_ACCESS = timedelta(seconds=parse(os.environ.get("JWT_TTL_ACCESS", "5 minutes"))) +JWT_TTL_APP_ACCESS = timedelta( + seconds=parse(os.environ.get("JWT_TTL_APP_ACCESS", "5 minutes")) +) +JWT_TTL_REFRESH = timedelta(seconds=parse(os.environ.get("JWT_TTL_REFRESH", "30 days"))) + + +JWT_TTL_REQUEST_EMAIL_CHANGE = timedelta( + seconds=parse(os.environ.get("JWT_TTL_REQUEST_EMAIL_CHANGE", "1 hour")), +) diff --git a/resources/saleor/template.env b/resources/saleor/template.env index 7493cfc..4af76bd 100644 --- a/resources/saleor/template.env +++ b/resources/saleor/template.env @@ -22,5 +22,6 @@ MEDIA_URL="{media}" #OPENEXCHANGERATES_API_KEY="{openxkey}" #GOOGLE_ANALYTICS_TRACKING_ID="{gatid}" +ADMIN_EMAIL="{adminemail}" DEBUG=False \ No newline at end of file