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