""" Django settings for flower project. Generated by 'django-admin startproject' using Django 5.2.7. For more information on this file, see https://docs.djangoproject.com/en/5.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.2/ref/settings/ """ # 兼容旧版 django-qiniu-storage try: from django.utils.encoding import force_text except ImportError: from django.utils.encoding import force_str as force_text import django.utils.encoding django.utils.encoding.force_text = force_text from datetime import timedelta from pathlib import Path from environ import Env # 应用七牛云 SDK 补丁(必须在导入其他模块之前) from flower.qiniu_patch import apply_all_patches apply_all_patches() env = Env( DEBUG=(bool, False), ) # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Initialize environment variables Env.read_env(str(BASE_DIR / '.env')) # 允许一次请求携带更多表单字段,避免 admin 批量操作时报 TooManyFieldsSent # Django 默认 DATA_UPLOAD_MAX_NUMBER_FIELDS=1000,当一次 POST/GET 的字段(含重复 key)过多会直接抛异常。 DATA_UPLOAD_MAX_NUMBER_FIELDS = env.int('DATA_UPLOAD_MAX_NUMBER_FIELDS', default=20000) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = env('SECRET_KEY', default='weak_secret_key_for_dev_only') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env('DEBUG', default=False) ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[ 'yuwenerp.yuwen.cloud', ]) CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[ 'https://yuwenerp.yuwen.cloud', ]) # 明道云同步配置 MDY_MERCHANT_ID = env.int('MDY_MERCHANT_ID', default=1) MDY_PRODUCT_CATEGORY_ID = env.int('MDY_PRODUCT_CATEGORY_ID', default=1) MDY_ISOLATED_MERCHANT_ID = env.int('MDY_ISOLATED_MERCHANT_ID', default=0) MDY_SYNC_PAGE_SIZE = env.int('MDY_SYNC_PAGE_SIZE', default=100) MDY_SYNC_MAX_PAGES = env.int('MDY_SYNC_MAX_PAGES', default=2) MDY_SYNC_MAX_RECORDS = env.int('MDY_SYNC_MAX_RECORDS', default=200) # 明道云“开版暂存”同步:每次请求之间的最小间隔(用于限流) # - 0.02 ~ 50 qps(理论值,实际应更保守以预留重试/关联查询) MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS = env.float('MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS', default=0.05) # Tencent Cloud (TIIA) 配置:用于通过 ImageUrl 上传图片到图库(CreateImage) # 注意:不要把 secret 写死在代码里,建议通过 .env / 环境变量注入。 TENCENTCLOUD_SECRET_ID = env('TENCENTCLOUD_SECRET_ID', default='AKID2wtgIhYCcprkS0QfiZCbopetxpW1KSPE') TENCENTCLOUD_SECRET_KEY = env('TENCENTCLOUD_SECRET_KEY', default='oSwGOJ0XzJRODrIXZWIrEPNAmHdppdHh') TENCENTCLOUD_TOKEN = env('TENCENTCLOUD_TOKEN', default='') # STS 临时票据(可选) TENCENTCLOUD_TIIA_GROUP_ID = env('TENCENTCLOUD_TIIA_GROUP_ID', default='168') TENCENTCLOUD_TIIA_REGION = env('TENCENTCLOUD_TIIA_REGION', default='ap-guangzhou') TENCENTCLOUD_TIIA_ENDPOINT = env('TENCENTCLOUD_TIIA_ENDPOINT', default='tiia.tencentcloudapi.com') TENCENTCLOUD_TIIA_PIC_NAME_PREFIX = env('TENCENTCLOUD_TIIA_PIC_NAME_PREFIX', default='plate_order') TENCENTCLOUD_TIIA_QPS = env.int('TENCENTCLOUD_TIIA_QPS', default=10) # Tencent API 限速:每秒最多 10 次 # 企业微信机器人 Webhook(目前按需求直接写入 settings,不走 .env) # 使用:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx WECOM_WEBHOOK_BASE_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send' WECOM_WEBHOOK_KEY = env('WECOM_WEBHOOK_KEY', default='') # CORS 配置 CORS_ALLOW_ALL_ORIGINS = DEBUG # 开发环境允许所有源,生产环境需要配置白名单 CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS', default=[ 'http://localhost:8000', 'http://127.0.0.1:5173', 'http://127.0.0.1:5174', 'http://127.0.0.1:5179', 'https://yuwenerp.yuwen.cloud', ]) CORS_ALLOW_CREDENTIALS = True # 允许携带凭证(如 Cookie、认证头) # SSE 需要的特殊 CORS 配置 CORS_ALLOW_HEADERS = [ 'accept', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'cache-control', # SSE 需要 'x-accel-buffering', # SSE 需要 'last-event-id', # SSE 重连需要 # 注意:不要添加 'connection',这是 hop-by-hop 头部,会被代理过滤 ] # SSE 需要暴露的响应头 CORS_EXPOSE_HEADERS = [ 'content-type', 'cache-control', 'x-accel-buffering', ] # 预检请求缓存时间(秒) CORS_PREFLIGHT_MAX_AGE = 86400 # 24小时 # Application definition INSTALLED_APPS = [ 'simpleui', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 使用七牛云存储,不需要staticfiles app 'corsheaders', # CORS 支持 'rest_framework', 'drf_spectacular', 'basic_info', 'business', 'stock', 'api_v1', 'api_man', 'printing', 'stateflow', 'sse', 'api_v2', 'shipment', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', # CORS 中间件,必须放在 CommonMiddleware 之前 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'flower.middleware.ApiAuditLogMiddleware', # API审计日志中间件,需在AuthenticationMiddleware之后 ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } # JWT 配置 SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=7), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'UPDATE_LAST_LOGIN': True, } SPECTACULAR_SETTINGS = { 'TITLE': 'Flower API', 'DESCRIPTION': 'API documentation for the Flower project', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, } ROOT_URLCONF = 'flower.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'flower.wsgi.application' # Database # https://docs.djangoproject.com/en/5.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST', default='localhost'), 'PORT': env('DB_PORT', default='6432'), 'CONN_HEALTH_CHECK': True, 'CONN_MAX_AGE': env.int('CONN_MAX_AGE', default=0), } } # Cache configuration # https://docs.djangoproject.com/en/5.2/topics/cache/ # Django 5.x 内置 Redis 缓存后端,使用 redis 包(已安装) # Redis 连接配置:优先使用 CACHE_URL,否则从 CACHE_HOST/CACHE_PORT/CACHE_DB 构建 # 默认 localhost:6379(适合宿主机运行),Docker Compose 可通过环境变量指定 CACHE_HOST=redis # 检测是否在测试环境中 import sys TESTING = 'test' in sys.argv if TESTING: # 测试环境使用内存缓存,避免 Redis 连接问题 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-testing-cache', } } else: # 生产/开发环境使用 Redis # 优先使用 CACHE_URL(完整 URL),否则从 host/port/db 构建 cache_url = env('CACHE_URL', default=None) if cache_url is None: cache_host = env('CACHE_HOST', default='localhost') cache_port = env.int('CACHE_PORT', default=6379) cache_db = env.int('CACHE_DB', default=0) cache_url = f'redis://{cache_host}:{cache_port}/{cache_db}' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': cache_url, 'OPTIONS': { # Redis 连接选项 'socket_connect_timeout': 5, # 连接超时(秒) 'socket_timeout': 5, # 操作超时(秒) 'retry_on_timeout': True, # 超时后重试 'health_check_interval': 30, # 健康检查间隔(秒) }, # 键前缀,避免不同项目/环境之间的键冲突 'KEY_PREFIX': env('CACHE_KEY_PREFIX', default='flower'), # 默认过期时间(秒),None 表示永不过期(由具体使用场景决定) 'TIMEOUT': env.int('CACHE_DEFAULT_TIMEOUT', default=300), # 默认 5 分钟 } } # PrintingJob:状态推进时的企业微信通知(markdown) # - 默认:测试环境关闭(避免单测出网/刷屏),非测试环境开启 PRINTING_JOB_STATE_ADVANCED_WECOM_NOTIFY_ENABLED = (not TESTING) # PrintingJob 跟进地址(用于企业微信通知里展示/点击) # orderId 使用 PrintingOrder.id PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE = env('PRINTING_JOB_STATE_ADVANCED_FOLLOWUP_URL_TEMPLATE', default='') PRINTING_JOB_STATE_ADVANCED_WECOM_MARKDOWN_TEMPLATE = ( "### 印染任务状态推进\n" "\n" "- **印染订单ID**:`{printing_order_id}`\n" "- **印染任务ID**:`{printing_job_id}`\n" "- **推进状态**:**{state_name}**\n" "- **推进时间**:`{advanced_at}`\n" "- **发送者**:{sender}\n" "{followup_line}" "\n" "**工序参数**:\n" "{process_params_markdown}\n" ) # PrintingOrder:创建时的企业微信通知(markdown) # - 默认:测试环境关闭(避免单测出网/刷屏),非测试环境开启 PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED = env.bool( "PRINTING_ORDER_CREATED_WECOM_NOTIFY_ENABLED", default=(not TESTING), ) PRINTING_ORDER_CREATED_WECOM_MARKDOWN_TEMPLATE = ( "### 印染订单创建\n" "\n" "- **印染订单ID**:`{printing_order_id}`\n" "- **订单编号**:`{printing_order_human_id}`\n" "- **创建时间**:`{created_at}`\n" "- **发送者**:{sender}\n" "- **客户**:{customer_name}\n" "- **面料**:{fabric}\n" "- **出货日期**:`{outgoing_date}`\n" ) # PreSalesOrder:创建时的企业微信通知(markdown) # - 默认:测试环境关闭(避免单测出网/刷屏),非测试环境开启 PRE_SALES_ORDER_CREATED_WECOM_NOTIFY_ENABLED = env.bool( "PRE_SALES_ORDER_CREATED_WECOM_NOTIFY_ENABLED", default=(not TESTING), ) # 预销售单跟进地址(用于企业微信通知里展示/点击) # 使用 pre_sales_order_id 进行格式化 PRE_SALES_ORDER_CREATED_FOLLOWUP_URL_TEMPLATE = env( "PRE_SALES_ORDER_CREATED_FOLLOWUP_URL_TEMPLATE", default="https://app.yuwen.cloud/workstation/warehouse/task-allocation/{pre_sales_order_id}", ) # Logging configuration # 目标: # - 生产环境出现 500 时,能在 stdout 日志里看到完整 traceback # - 所有日志都带时间戳(含毫秒),便于快速定位与检索 LOG_LEVEL = env('LOG_LEVEL', default='INFO').upper() DJANGO_LOG_LEVEL = env('DJANGO_LOG_LEVEL', default=LOG_LEVEL).upper() UVICORN_LOG_LEVEL = env('UVICORN_LOG_LEVEL', default=LOG_LEVEL).upper() UVICORN_ACCESS_LOG_LEVEL = env('UVICORN_ACCESS_LOG_LEVEL', default='INFO').upper() LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'default': { # 注意:Python logging 的 datefmt 基于 time.strftime,不支持 %f; # 这里用 %(msecs)03d 输出毫秒。 'format': '%(asctime)s.%(msecs)03d [%(levelname)s] %(name)s: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'default', }, }, 'root': { 'handlers': ['console'], 'level': LOG_LEVEL, }, 'loggers': { # Django 在发生未捕获异常(500)时,会通过 django.request 输出错误日志, # record.exc_info 会携带 traceback,Formatter 会自动把 traceback 打到日志中。 'django.request': { 'handlers': ['console'], 'level': 'ERROR', 'propagate': False, }, 'django': { 'handlers': ['console'], 'level': DJANGO_LOG_LEVEL, 'propagate': False, }, # Uvicorn 日志(access/error)统一走我们的 console formatter,确保有时间戳 'uvicorn': { 'handlers': ['console'], 'level': UVICORN_LOG_LEVEL, 'propagate': False, }, 'uvicorn.error': { 'handlers': ['console'], 'level': UVICORN_LOG_LEVEL, 'propagate': False, }, 'uvicorn.access': { 'handlers': ['console'], 'level': UVICORN_ACCESS_LOG_LEVEL, 'propagate': False, }, }, } # Password validation # https://docs.djangoproject.com/en/5.2/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/5.2/topics/i18n/ LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.2/howto/static-files/ # 七牛云存储配置 QINIU_ACCESS_KEY = 'IySZff0zEN7jHeDuxj5PrQNMv0qjKoVqBRCuFMzi' QINIU_SECRET_KEY = 'Mph5XkjUQO3VHFPT1JulYHY442uMOya2bXjbSZp8' QINIU_BUCKET_NAME = 'yunwenerp' QINIU_BUCKET_DOMAIN = 'image.yuwen.cloud' QINIU_SECURE_URL = True STORAGES = { 'default': { 'BACKEND': 'qiniustorage.backends.QiniuStorage', }, 'staticfiles': { 'BACKEND': 'qiniustorage.backends.QiniuStorage', }, } DEFAULT_FILE_STORAGE = 'qiniustorage.backends.QiniuStorage' STATICFILES_STORAGE = 'qiniustorage.backends.QiniuStorage' # 静态文件URL(直接从七牛云访问) STATIC_URL = f'http://{QINIU_BUCKET_DOMAIN}/static/' STATIC_ROOT = 'static' MEDIA_URL = f'http://{QINIU_BUCKET_DOMAIN}/media/' # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # Stateflow 配置 # ------------------------------------------------------------------------------ # STATEFLOW_CURRENT_STATE_MODE: 控制 current_state 的语义 # # 可选值: # 'NEXT' (默认): current_state 返回"下一个待执行的节点" # - 配合 progress 判断:"节点名" / "已完成" # - 适合显示"接下来要做什么" # - 原有默认行为 # # 'LAST': current_state 返回"最后完成的节点" # - 显示"刚做完了什么" # - 适合审计和历史查看 # - 新增行为模式 # # 注意: # - 修改此配置会影响 status 属性的显示内容 # - API 接口保持兼容,但返回的 status 和 status_id 语义会变化 # - 推荐在新代码中直接使用 get_last_completed_state() 或 get_next_pending_state() # 以避免歧义 # - 已废除"未开始"的显示,直接显示第一个节点名称 # ------------------------------------------------------------------------------ STATEFLOW_CURRENT_STATE_MODE = 'NEXT' # STATEFLOW_CURRENT_STATE_MODE = 'LAST' # Printing module settings PRINTING_DEFAULT_PROCESS_ID = 1 # 默认印染流程ID PLATE_ORDER_DEFAULT_PROCESS_ID = 2 # 默认开版流程ID # 销售品自动创建配置(PrintingJob 流程完成时) # ------------------------------------------------------------------------------ # 指定从哪个流程节点获取数量参数,单位固定为"米" # 如果该节点未完成或参数不存在,则不创建销售品 # ------------------------------------------------------------------------------ AUTO_CREATE_SALESITEM_FROM_PRINT_ORDER = env.bool('AUTO_CREATE_SALESITEM_FROM_PRINT_ORDER', default=False) PRINTING_SALES_ITEM_SOURCE_STATE_ID = env.int('PRINTING_SALES_ITEM_SOURCE_STATE_ID') # 必须在 .env 中配置 PRINTING_SALES_ITEM_QUANTITY_KEY = '米数' # 数量参数的 key # API 审计日志配置 # ------------------------------------------------------------------------------ # 用于记录特定模块POST请求的历史现场,便于追踪"创建"操作。 # 通过Celery异步写入,不影响API响应性能。 # ------------------------------------------------------------------------------ API_AUDIT_LOG_ENABLED = True API_AUDIT_LOG_URL_PREFIXES = [ '/api/v1/printing', '/api/v1/plate', ] # Celery 配置 # RabbitMQ Broker 配置:优先使用 CELERY_BROKER_URL,否则从各组件构建 # 默认 localhost(适合宿主机运行),Docker Compose 可通过环境变量指定 CELERY_BROKER_HOST=rabbitmq celery_broker_url = env('CELERY_BROKER_URL', default=None) if celery_broker_url is None: celery_broker_host = env('CELERY_BROKER_HOST', default='localhost') celery_broker_port = env.int('CELERY_BROKER_PORT', default=5672) celery_broker_user = env('CELERY_BROKER_USER', default='guest') celery_broker_password = env('CELERY_BROKER_PASSWORD', default='guest') celery_broker_vhost = env('CELERY_BROKER_VHOST', default='/') celery_broker_url = f'amqp://{celery_broker_user}:{celery_broker_password}@{celery_broker_host}:{celery_broker_port}/{celery_broker_vhost}' CELERY_BROKER_URL = celery_broker_url # Celery Result Backend 配置:优先使用 CELERY_RESULT_BACKEND,否则从各组件构建 # 默认使用与 Cache 相同的 Redis 实例,Docker Compose 可通过环境变量指定 CELERY_RESULT_HOST=redis celery_result_url = env('CELERY_RESULT_BACKEND', default=None) if celery_result_url is None: celery_result_host = env('CELERY_RESULT_HOST', default='localhost') celery_result_port = env.int('CELERY_RESULT_PORT', default=6379) celery_result_db = env.int('CELERY_RESULT_DB', default=1) # 使用 db=1,与 Cache 的 db=0 区分 celery_result_url = f'redis://{celery_result_host}:{celery_result_port}/{celery_result_db}' CELERY_RESULT_BACKEND = celery_result_url CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE from celery.schedules import crontab CELERY_BEAT_SCHEDULE = { 'daily_database_backup': { 'task': 'api_v1.tasks.backup_database', 'schedule': crontab(hour=3, minute=0), 'kwargs': { 'output_dir': str(BASE_DIR / 'data-bak'), 'filename_prefix': 'db-backup', }, }, # 'daily_plate_order_tiia_image_upload': { # 'task': 'printing.tasks.upload_yesterday_plate_order_images_to_tencent_tiia', # 'schedule': crontab(hour=3, minute=0), # }, # 'daily_mdy_plate_order_staging_tiia_image_upload': { # 'task': 'api_v1.tasks.upload_mdy_plate_order_staging_images_to_tencent_tiia', # 'schedule': crontab(hour=3, minute=20), # 'kwargs': { # 'batch_size': 500, # 'dry_run': False, # }, # }, 'mdy_product_sync': { 'task': 'api_v1.tasks.sync_mdy_products', 'schedule': crontab(minute='*/10'), 'kwargs': { 'page_size': MDY_SYNC_PAGE_SIZE, 'max_pages': MDY_SYNC_MAX_PAGES, 'max_records': MDY_SYNC_MAX_RECORDS, }, }, 'mdy_customer_sync': { 'task': 'api_v1.tasks.sync_mdy_customers', 'schedule': crontab(minute='*/5'), 'kwargs': { 'page_size': MDY_SYNC_PAGE_SIZE, 'max_pages': MDY_SYNC_MAX_PAGES, 'max_records': MDY_SYNC_MAX_RECORDS, }, }, # 明道云开版:同步到“暂存表” # - 仅在 02:00-08:00 时间窗内持续运行(每 N 分钟触发一次) # - 通过 request_interval_seconds 控制单次任务对明道云 API 的请求节奏,避免超 QPS 'mdy_plate_order_staging_sync': { 'task': 'api_v1.tasks.sync_mdy_plate_orders', # 02:00 - 07:59(不包含 08:xx) 'schedule': crontab(minute='*/2', hour='2-7'), 'kwargs': { 'page_size': max(1, int(MDY_SYNC_PAGE_SIZE)), 'max_pages': max(1, int(MDY_SYNC_MAX_PAGES)), 'max_records': max(1, int(MDY_SYNC_MAX_RECORDS)), 'with_related': True, 'max_related_per_type': 5, 'request_interval_seconds': float(MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS), }, }, } if MDY_ISOLATED_MERCHANT_ID and MDY_ISOLATED_MERCHANT_ID > 0: CELERY_BEAT_SCHEDULE.update( { 'mdy_product_sync_isolated': { 'task': 'api_v1.tasks.sync_mdy_products_for_merchant', 'schedule': crontab(minute='3,13,23,33,43,53'), 'kwargs': { 'merchant_id': MDY_ISOLATED_MERCHANT_ID, 'page_size': MDY_SYNC_PAGE_SIZE, 'max_pages': MDY_SYNC_MAX_PAGES, 'max_records': MDY_SYNC_MAX_RECORDS, }, }, # 'mdy_customer_sync_isolated': { # 'task': 'api_v1.tasks.sync_mdy_customers_for_merchant', # 'schedule': crontab(minute='1,11,21,31,41,51'), # 'kwargs': { # 'merchant_id': MDY_ISOLATED_MERCHANT_ID, # 'page_size': MDY_SYNC_PAGE_SIZE, # 'max_pages': MDY_SYNC_MAX_PAGES, # 'max_records': MDY_SYNC_MAX_RECORDS, # }, # }, # 'mdy_plate_order_staging_sync_isolated': { # 'task': 'api_v1.tasks.sync_mdy_plate_orders_for_merchant', # 'schedule': crontab(minute='*/2', hour='8-11'), # 'kwargs': { # 'merchant_id': MDY_ISOLATED_MERCHANT_ID, # 'page_size': max(1, int(MDY_SYNC_PAGE_SIZE)), # 'max_pages': max(1, int(MDY_SYNC_MAX_PAGES)), # 'max_records': max(1, int(MDY_SYNC_MAX_RECORDS)), # 'with_related': True, # 'max_related_per_type': 5, # 'request_interval_seconds': float(MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS), # }, # }, } )