diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4942dd8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+env/**
+venv/**
+.idea/**
+.vscode/**
+aitrainer_backoffice/aitrainer_backoffice/__pycache__/**
+aitrainer_backoffice/aitrainer_backoffice/**/__pycache__/**
+aitrainer_backoffice/controlling/__pycache__/**
+aitrainer_backoffice/controlling/**/__pycache__
+aitrainer_backoffice/aitrainer_backoffice/media/**
+aitrainer_backoffice/aitrainer_backoffice/static/**
\ No newline at end of file
diff --git a/.key b/.key
index 718bbd4..9ab8c62 100644
--- a/.key
+++ b/.key
@@ -1,3 +1,2 @@
-DJANGO_SETTINGS_MODULE=aitrainer_backoffice/aitrainer_backoffice/settings/prod.py
MYSQL_ROOT_PASSWORD=andio2009
MYSQL_USER=root
\ No newline at end of file
diff --git a/Docker/,key b/Docker/,key
deleted file mode 100644
index 718bbd4..0000000
--- a/Docker/,key
+++ /dev/null
@@ -1,3 +0,0 @@
-DJANGO_SETTINGS_MODULE=aitrainer_backoffice/aitrainer_backoffice/settings/prod.py
-MYSQL_ROOT_PASSWORD=andio2009
-MYSQL_USER=root
\ No newline at end of file
diff --git a/Docker/docker_backup.sh b/Docker/docker_backup.sh
new file mode 100644
index 0000000..b03785f
--- /dev/null
+++ b/Docker/docker_backup.sh
@@ -0,0 +1,56 @@
+#docker run --name mailcow-backup --rm \
+# -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
+# -v $(docker volume ls -qf name=${CMPS_PRJ}_crypt-vol-1):/crypt:ro \
+# ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable --best" -Pcvpf /backup/backup_crypt.tar.gz /crypt
+
+#listVar="ps17 wp vmail iredadmin amavisd iredapd mysql performance_schema phpmyadmin piwik postfix psclub roundcubemail sys aitrainer"
+#for i in $listVar; do
+
+#backupfile=/root/backup/Mautic_`date '+%Y-%m-%d_%H_%M_%S'`.sql.gz
+#mysqldump -u psdemo -h localhost --single-transaction --quick --lock-tables=false mautic | gzip > $backupfile
+
+#listVar="ps17 wp vmail iredadmin amavisd iredapd mysql performance_schema phpmyadmin piwik postfix psclub roundcubemail sys aitrainer"
+
+#for i in $listVar; do
+# echo "backup db $i..."
+# backupfile=/root/backup/"$i"_`date '+%Y-%m-%d_%H_%M_%S'`.sql.gz
+# mysqldump -u psdemo -h localhost --single-transaction --quick --lock-tables=false "$i" | gzip > $backupfile
+# sshpass -f /root/.ssh/.scp scp $backupfile bosi@aitrainer.app:/home/bosi/backup/"$i".sql.gz
+#done
+
+#docker ps | sed -n 2,100p | grep -v "mailcow" | sed 's/\([^:]*\).*/\1/' | sed 's/\//_/g' | sed 's/\([^"]*\).*/\1/'
+
+
+#BACKUP_LOCATION=/home/bosi/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all --delete-days 30
+BACKUP_LOCATION=/home/bosi/backup
+
+
+ container=mysql
+ echo "backup container $container"
+ DATE=$(date +"%Y-%m-%d-%H-%M-%S")
+ dir="${BACKUP_LOCATION}/mysql-${DATE}"
+ mkdir -p $dir
+ chmod 755 $dir
+ cd $dir
+
+ container_id=$(docker ps | grep "mysql:8.0.21" | sed 's/\([^ ]*\).*/\1/')
+ docker commit -p $container_id backup_mysql
+ docker save -o backup_mysql.tar backup_mysql
+
+echo "backup configs"
+cp /etc/nginx/sites-enabled/* $dir/
+
+config_files="requirements.txt docker-compose.sh docker-compose.yml uwsgi_params Dockerfile wp_php_custom.ini wp_htaccess htpasswd phpmyadmin.config.php"
+for i in $config_files; do
+ cp /home/bosi/backoffice/aitrainer_backoffice/$i $dir/
+done
+
+config_dirs="mysqlconf api api_test"
+for i in $config_dirs; do
+ mkdir $dir/$i
+ cp -r /home/bosi/backoffice/aitrainer_backoffice/$i/* $dir/$i/
+done
+
+echo "backup mailcow"
+BACKUP_LOCATION=/home/bosi/backup /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup all --delete-days 30
+
diff --git a/aitrainer_backoffice/aitrainer_backoffice/admin/inline_select_action.py b/aitrainer_backoffice/aitrainer_backoffice/admin/inline_select_action.py
index e69de29..9af263b 100644
--- a/aitrainer_backoffice/aitrainer_backoffice/admin/inline_select_action.py
+++ b/aitrainer_backoffice/aitrainer_backoffice/admin/inline_select_action.py
@@ -0,0 +1,117 @@
+from typing import Callable, List, Optional, Union
+from django.contrib import admin
+from django.utils.safestring import mark_safe
+from django.utils.text import capfirst
+from django.utils.translation import ugettext_lazy as _
+
+
+class BaseInlineSelectActionsMixin:
+ INLINE_MODEL_ADMIN = 'inline_select'
+ MODEL_ADMIN = 'admin'
+
+ inline_select_actions: Optional[List[Union[str, Callable]]] = []
+
+ def get_inline_select_actions(self, obj=None):
+ """
+ Returns a list of all actions for this Admin.
+ """
+ # If self.actions is explicitly set to None that means that we don't
+ # want *any* actions enabled on this page.
+ if self.inline_select_actions is None:
+ return []
+
+ actions = []
+
+ # Gather actions from the inline admin and all parent classes,
+ # starting with self and working back up.
+ for klass in self.__class__.mro()[::-1]:
+ class_actions = getattr(klass, 'inline_select_actions', [])
+ # Avoid trying to iterate over None
+ if not class_actions:
+ continue
+
+ for action in class_actions:
+ if action not in actions:
+ actions.append(action)
+
+ return actions
+
+ def get_readonly_fields(self, request, obj=None):
+ fields = super().get_readonly_fields(request, obj)
+ fields = list(fields)
+
+ if 'render_inline_select_actions' not in fields:
+ fields.append('render_inline_select_actions')
+ return fields
+
+ def _get_admin_type(self, model_admin=None):
+ """
+ Returns wether this is an InlineAdmin or not.
+ """
+ model_admin = model_admin or self
+
+ if isinstance(model_admin, admin.options.InlineModelAdmin):
+ return self.INLINE_MODEL_ADMIN
+ return self.MODEL_ADMIN
+
+ def render_inline_select_actions(self, obj=None): # NOQA: C901
+ """
+ Renders all defined inline actions as html.
+ """
+ if not (obj and obj.pk):
+ return ''
+
+ buttons = []
+ actions = self.get_inline_select_actions(obj)
+ for action_name in actions:
+ action_func = getattr(self, action_name, None)
+ if not action_func:
+ raise RuntimeError("Could not find action `{}`".format(action_name))
+
+ # Add per-object label support
+ action_name = action_func.__name__
+ label_handler = getattr(self, 'get_{}_label'.format(action_name), None)
+ if callable(label_handler):
+ description = label_handler(obj=obj)
+ else:
+ try:
+ description = action_func.short_description
+ except AttributeError:
+ description = capfirst(action_name.replace('_', ' '))
+
+ # Add per-object css classes support
+ css_handler = getattr(self, 'get_{}_css'.format(action_name), None)
+ if callable(css_handler):
+ css_classes = css_handler(obj=obj)
+ else:
+ try:
+ css_classes = action_func.css_classes
+ except AttributeError:
+ css_classes = ''
+
+ # If the form is submitted, we have no information about the
+ # requested action.
+ # Hence we need all data to be encoded using the action name.
+ action_data = [
+ # required to distinguish between multiple inlines for the same model
+ self.__class__.__name__.lower(),
+ self._get_admin_type(),
+ action_name,
+ obj._meta.app_label,
+ obj._meta.model_name,
+ str(obj.pk),
+ ]
+ buttons.append(
+ ''.format(
+ '_action__{}'.format('__'.join(action_data)),
+ css_classes,
+ )
+ )
+
+ print("Buttons " + str(buttons))
+ return mark_safe(
+ '
{}
'.format(''.join(buttons))
+ )
+
+ render_inline_select_actions.short_description = _("Actions") # type: ignore
+ render_inline_select_actions.allow_tags = True # type: ignore
diff --git a/aitrainer_backoffice/aitrainer_backoffice/admin/training_plan.py b/aitrainer_backoffice/aitrainer_backoffice/admin/training_plan.py
index 121a497..50aed05 100644
--- a/aitrainer_backoffice/aitrainer_backoffice/admin/training_plan.py
+++ b/aitrainer_backoffice/aitrainer_backoffice/admin/training_plan.py
@@ -2,13 +2,12 @@ from adminsortable2.admin import SortableAdminMixin, SortableInlineAdminMixin
from django.contrib import admin
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
-from inline_actions.admin import InlineActionsMixin
-from inline_actions.admin import InlineActionsModelAdminMixin
+from .inline_select_action import BaseInlineSelectActionsMixin
from ..models.training_plan import TrainingPlan, TrainingPlanDetail, TrainingPlanTranslation
-class TrainingPlanDetailInline(InlineActionsMixin, SortableInlineAdminMixin, admin.TabularInline):
+class TrainingPlanDetailInline(BaseInlineSelectActionsMixin, SortableInlineAdminMixin, admin.TabularInline):
model = TrainingPlanDetail
extra = 0
list_display = (
@@ -19,6 +18,8 @@ class TrainingPlanDetailInline(InlineActionsMixin, SortableInlineAdminMixin, adm
list_display_links = ('sort', )
ordering = ('sort',)
+ inline_select_actions = ['copy_attributes',]
+
def repeat_max(self, obj):
if obj.repeat_max:
obj.repeats = -1
@@ -45,6 +46,7 @@ class TrainingPlanDetailInline(InlineActionsMixin, SortableInlineAdminMixin, adm
actions = super(TrainingPlanDetailInline, self).get_inline_actions(request, obj)
return actions
+ @admin.action(description='clone')
def copy_attributes(self, request, obj, parent_obj=None):
name = str(request.queryset[0].training_plan)
@@ -96,11 +98,15 @@ class TranslationTrainingPlanInline(admin.TabularInline):
extra = 0
-class TrainingPlanAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin):
+class TrainingPlanAdmin( admin.ModelAdmin):
list_display = ('training_plan_id', 'name', 'internal_name', 'free', 'active')
fields = ('tree', 'name', 'description', 'internal_name', 'free', 'active')
list_editable = ('name', 'internal_name', 'free', 'active')
+ def save_model(self, request, obj, form, change):
+ print(request)
+ super(TrainingPlanAdmin, self).save_model(request, obj, form, change)
+
inlines = [
TranslationTrainingPlanInline,
TrainingPlanDetailInline
diff --git a/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py b/aitrainer_backoffice/aitrainer_backoffice/settings.py
similarity index 79%
rename from aitrainer_backoffice/aitrainer_backoffice/settings/dev.py
rename to aitrainer_backoffice/aitrainer_backoffice/settings.py
index a2a720b..6a68d8d 100644
--- a/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py
+++ b/aitrainer_backoffice/aitrainer_backoffice/settings.py
@@ -1,38 +1,19 @@
-"""
-Django settings for aitrainer_backoffice project. (development)
-
-Generated by 'django-admin startproject' using Django 3.0.8.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/3.0/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/3.0/ref/settings/
-"""
-
import os
-BACKOFFICE_VERSION = 1.27
+BACKOFFICE_VERSION = 1.28
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-#BASE_DIR = Path(__file__).resolve().parent.parent.parent
-LOCALE_PATHS = [
- 'D:\\projects\\aitrainer\\src\\aitrainer_backoffice\\aitrainer_backoffice\\locale',
- os.path.join(BASE_DIR, 'locale')
-]
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
+LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ['DJANGO_KEY']
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = False
-ALLOWED_HOSTS = ['localhost']
+ALLOWED_HOSTS = ['62.171.188.119', "admin.aitrainer.app"]
# Application definition
@@ -69,7 +50,7 @@ ROOT_URLCONF = 'aitrainer_backoffice.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- "DIRS": [os.path.join(BASE_DIR, "templates"), ],
+ 'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -90,19 +71,19 @@ WSGI_APPLICATION = 'aitrainer_backoffice.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'aitrainer2',
+ 'NAME': 'aitrainer_test',
'USER': 'aitrainer',
'PASSWORD': 'andio2009',
- 'HOST': '127.0.0.1',
- 'PORT': 3306
+ 'HOST': '62.171.188.119',
+ 'PORT': 33060
},
'live': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'aitrainer',
'USER': 'aitrainer',
'PASSWORD': 'andio2009',
- 'HOST': '127.0.0.1',
- 'PORT': 3306
+ 'HOST': '62.171.188.119',
+ 'PORT': 33060
}
}
@@ -129,7 +110,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
-LANGUAGE_CODE = 'hu'
+LANGUAGE_CODE = 'hu-HU'
TIME_ZONE = 'Europe/Budapest'
@@ -144,8 +125,6 @@ USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
-STATIC_JS_DIR = os.path.join(STATIC_URL, "js")
-
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
@@ -174,7 +153,13 @@ LOGGING = {
},
}
+# deployment settings
+#SECURE_SSL_REDIRECT = False
+#SESSION_COOKIE_SECURE = True
+#CSRF_COOKIE_SECURE = True
+
+
CRON_CLASSES = [
'aitrainer_backoffice.controlling.cron.sync_customers',
# ...
-]
\ No newline at end of file
+]
diff --git a/aitrainer_backoffice/aitrainer_backoffice/settings/__init__.py b/aitrainer_backoffice/aitrainer_backoffice/settings/__init__.py
deleted file mode 100644
index be7ce5e..0000000
--- a/aitrainer_backoffice/aitrainer_backoffice/settings/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .prod import *
diff --git a/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py b/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py
deleted file mode 100644
index 9379de2..0000000
--- a/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py
+++ /dev/null
@@ -1,192 +0,0 @@
-"""
-Django settings for aitrainer_backoffice project.
-
-Generated by 'django-admin startproject' using Django 3.0.8.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/3.0/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/3.0/ref/settings/
-"""
-
-import os
-
-BACKOFFICE_VERSION = 1.27
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = '9874959872==9847588jkklnkln$asdf' #os.environ['DJANGO_KEY']
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = ['62.171.188.119', 'localhost', 'andio.eu', 'aitrainer.info','aitrainer.app', 'admin.aitrainer.info',
- "admin.aitrainer.app"]
-
-# Application definition
-
-INSTALLED_APPS = [
- 'aitrainer_backoffice.aitrainer_backoffice',
- 'aitrainer_backoffice.controlling.apps.ControllingConfigLive',
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'ckeditor',
- 'ckeditor_uploader',
- 'django_admin_json_editor',
- 'rangefilter',
- 'adminsortable2',
- 'inline_actions',
- 'django_cron',
-]
-
-MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
-]
-
-ROOT_URLCONF = 'aitrainer_backoffice.aitrainer_backoffice.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 = 'aitrainer_backoffice.wsgi.application'
-
-# Database
-# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'aitrainer_test',
- 'USER': 'aitrainer',
- 'PASSWORD': 'andio2009',
- 'HOST': '62.171.188.119',
- 'PORT': 33060
- },
- 'live': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'aitrainer',
- 'USER': 'aitrainer',
- 'PASSWORD': 'andio2009',
- 'HOST': '62.171.188.119',
- 'PORT': 33060
- }
-}
-
-DATABASE_ROUTERS = ['aitrainer_backoffice.aitrainer_backoffice.db_router.TestRouter']
-
-# Password validation
-# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
-]
-
-# Internationalization
-# https://docs.djangoproject.com/en/3.0/topics/i18n/
-
-LANGUAGE_CODE = 'hu-HU'
-
-TIME_ZONE = 'Europe/Budapest'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/3.0/howto/static-files/
-
-STATIC_URL = '/static/'
-STATIC_ROOT = os.path.join(BASE_DIR, "static")
-
-MEDIA_URL = '/media/'
-MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-CKEDITOR_UPLOAD_PATH = MEDIA_URL
-
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'filters': {
- 'require_debug_true': {
- '()': 'django.utils.log.RequireDebugTrue',
- },
- },
- 'handlers': {
- 'file': {
- 'level': 'ERROR',
- 'class': 'logging.FileHandler',
- 'filename': '/var/log/django_error.log',
- }
- },
- 'loggers': {
- 'django': {
- 'handlers': ['file'],
- 'level': 'DEBUG',
- 'propagate': True,
- },
- },
-}
-
-# deployment settings
-#SECURE_SSL_REDIRECT = False
-#SESSION_COOKIE_SECURE = True
-#CSRF_COOKIE_SECURE = True
-
-CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
- 'LOCATION': '/var/tmp/django_cache',
- 'TIMEOUT': 300,
- 'OPTIONS': {
- 'MAX_ENTRIES': 50000
- }
- }
-}
-
-CRON_CLASSES = [
- 'aitrainer_backoffice.aitrainer_backoffice.controlling.cron.sync_customers',
- # ...
-]
diff --git a/aitrainer_backoffice/cron.txt b/aitrainer_backoffice/cron.txt
index e69de29..18f3392 100644
--- a/aitrainer_backoffice/cron.txt
+++ b/aitrainer_backoffice/cron.txt
@@ -0,0 +1 @@
+*/5 * * * * python /aitrainer_backoffice/aitrainer_backoffice/manage.py runcrons > /var/log/cronjob.log
\ No newline at end of file
diff --git a/aitrainer_backoffice/manage.py b/aitrainer_backoffice/manage.py
index c56b98a..30dfef0 100644
--- a/aitrainer_backoffice/manage.py
+++ b/aitrainer_backoffice/manage.py
@@ -5,7 +5,7 @@ import sys
def main():
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aitrainer_backoffice.settings.dev')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'aitrainer_backoffice.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
diff --git a/aitrainer_backoffice/translate_compile.bat b/aitrainer_backoffice/translate_compile.bat
index 26a7027..abbb062 100644
--- a/aitrainer_backoffice/translate_compile.bat
+++ b/aitrainer_backoffice/translate_compile.bat
@@ -1,2 +1,2 @@
-django-admin compilemessages -l hu --pythonpath "D:\projects\aitrainer\src\aitrainer_backoffice" --settings aitrainer_backoffice.aitrainer_backoffice.settings.dev
+django-admin compilemessages -l hu --pythonpath "D:\projects\aitrainer\src\aitrainer_backoffice"
diff --git a/docker-compose.yml b/docker-compose.yml
index 7311b3f..efc6b86 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,7 +12,7 @@ services:
- mysql-server
ports:
- "8002:8000"
- command: gunicorn aitrainer_backoffice.aitrainer_backoffice.wsgi --env DJANGO_SETTINGS_MODULE=aitrainer_backoffice.aitrainer_backoffice.settings.prod --bind 0.0.0.0:8000 --workers 3
+ command: gunicorn aitrainer_backoffice.aitrainer_backoffice.wsgi --bind 0.0.0.0:8000 --workers 3
mysql-server:
image: mysql:8.0.21