fix: resolve setup and Docker issues preventing project from running
Use shared_task instead of standalone Celery apps so celery -A crm worker discovers all tasks. Add safe defaults for all required env vars in settings.py so missing .env no longer crashes Django. Create idempotent management command to bootstrap a superuser in Docker. Populate .env.example with working dev defaults. Fixes #490, #493, #508, #388, #383, #382, #381, #441, #519 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1f6b797909
commit
cbcab8636a
13 changed files with 94 additions and 72 deletions
|
|
@ -28,6 +28,7 @@ CELERY_RESULT_BACKEND=redis://redis:6379/0
|
|||
# Email (console backend in dev — emails print to stdout)
|
||||
DEFAULT_FROM_EMAIL=noreply@localhost
|
||||
ADMIN_EMAIL=admin@localhost
|
||||
ADMIN_PASSWORD=admin
|
||||
|
||||
# CORS
|
||||
CORS_ALLOW_ALL=True
|
||||
|
|
|
|||
|
|
@ -1,43 +1,41 @@
|
|||
# Environment variables
|
||||
# Copy this file to .env and adjust as needed: cp .env.example .env
|
||||
|
||||
SECRET_KEY=""
|
||||
SECRET_KEY="django-insecure-dev-key-please-change-in-production"
|
||||
ENV_TYPE="dev"
|
||||
DOMAIN_NAME=""
|
||||
DEBUG=True
|
||||
DOMAIN_NAME="http://localhost:8000"
|
||||
SWAGGER_ROOT_URL="http://localhost:8000"
|
||||
|
||||
# AWS
|
||||
# AWS (only needed for production / S3 storage)
|
||||
AWS_BUCKET_NAME=""
|
||||
|
||||
AWS_ACCESS_KEY_ID=""
|
||||
AWS_SECRET_ACCESS_KEY=""
|
||||
AWS_SES_REGION_NAME=""
|
||||
AWS_SES_REGION_ENDPOINT=""
|
||||
|
||||
|
||||
# DB
|
||||
DBNAME=""
|
||||
DBUSER=""
|
||||
DBPASSWORD=""
|
||||
DBHOST=""
|
||||
DBPORT=""
|
||||
DBNAME="crm_db"
|
||||
DBUSER="postgres"
|
||||
DBPASSWORD="postgres"
|
||||
DBHOST="localhost"
|
||||
DBPORT="5432"
|
||||
|
||||
# Sentry
|
||||
# Sentry (optional)
|
||||
SENTRY_DSN=""
|
||||
|
||||
# Celery
|
||||
CELERY_BROKER_URL="redis://localhost:6379/0"
|
||||
CELERY_RESULT_BACKEND="redis://localhost:6379/0"
|
||||
|
||||
# Swagger
|
||||
SWAGGER_ROOT_URL=""
|
||||
|
||||
# Google OAuth
|
||||
# Google OAuth (optional — leave blank to disable)
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
# Email
|
||||
DEFAULT_FROM_EMAIL=""
|
||||
ADMIN_EMAIL=""
|
||||
DEFAULT_FROM_EMAIL="noreply@localhost"
|
||||
ADMIN_EMAIL="admin@localhost"
|
||||
|
||||
ALLOWED_HOSTS=yourdomain.com
|
||||
CORS_ALLOWED_ORIGINS=https://yourdomain.com
|
||||
CSRF_TRUSTED_ORIGINS=https://yourdomain.com
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
CSRF_TRUSTED_ORIGINS=http://localhost:5173
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.template import Context, Template
|
||||
|
|
@ -8,10 +8,8 @@ from accounts.models import Account, AccountEmail, AccountEmailLog
|
|||
from common.models import Profile
|
||||
from common.tasks import set_rls_context
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email(email_obj_id, org_id):
|
||||
set_rls_context(org_id)
|
||||
email_obj = AccountEmail.objects.filter(id=email_obj_id).first()
|
||||
|
|
@ -58,7 +56,7 @@ def send_email(email_obj_id, org_id):
|
|||
pass
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_assigned_user(recipients, account_id, org_id):
|
||||
"""Send Mail To Users When they are assigned to an account"""
|
||||
set_rls_context(org_id)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.template.loader import render_to_string
|
||||
|
|
@ -7,10 +7,8 @@ from cases.models import Case
|
|||
from common.models import Profile
|
||||
from common.tasks import set_rls_context
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_assigned_user(recipients, case_id, org_id):
|
||||
"""Send Mail To Users When they are assigned to a case"""
|
||||
set_rls_context(org_id)
|
||||
|
|
|
|||
25
backend/common/management/commands/create_default_admin.py
Normal file
25
backend/common/management/commands/create_default_admin.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import os
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create a default superuser if none exists (for Docker bootstrap)"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if User.objects.filter(is_superuser=True).exists():
|
||||
self.stdout.write(self.style.SUCCESS("Superuser already exists — skipping."))
|
||||
return
|
||||
|
||||
email = os.environ.get("ADMIN_EMAIL", "admin@localhost")
|
||||
password = os.environ.get("ADMIN_PASSWORD", "admin")
|
||||
|
||||
User.objects.create_superuser(
|
||||
username=email,
|
||||
email=email,
|
||||
password=password,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f"Created default superuser: {email}"))
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
|
||||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.core.mail import EmailMessage
|
||||
|
|
@ -13,8 +13,6 @@ from django.utils.http import urlsafe_base64_encode
|
|||
from common.models import Comment, Profile, Teams, User
|
||||
from common.token_generator import account_activation_token
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
def set_rls_context(org_id):
|
||||
"""
|
||||
|
|
@ -33,7 +31,7 @@ def set_rls_context(org_id):
|
|||
)
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_new_user(user_id):
|
||||
"""Send Mail To Users When their account is created"""
|
||||
user_obj = User.objects.filter(id=user_id).first()
|
||||
|
|
@ -75,7 +73,7 @@ def send_email_to_new_user(user_id):
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_user_mentions(
|
||||
comment_id,
|
||||
called_from,
|
||||
|
|
@ -145,7 +143,7 @@ def send_email_user_mentions(
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_user_status(
|
||||
user_id,
|
||||
status_changed_user="",
|
||||
|
|
@ -185,7 +183,7 @@ def send_email_user_status(
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_user_delete(
|
||||
user_email,
|
||||
deleted_by="",
|
||||
|
|
@ -211,7 +209,7 @@ def send_email_user_delete(
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def resend_activation_link_to_user(
|
||||
user_email="",
|
||||
):
|
||||
|
|
@ -260,7 +258,7 @@ def resend_activation_link_to_user(
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def remove_users(removed_users_list, team_id, org_id=None):
|
||||
# Set RLS context for org-scoped queries
|
||||
set_rls_context(org_id)
|
||||
|
|
@ -311,7 +309,7 @@ def remove_users(removed_users_list, team_id, org_id=None):
|
|||
invoice.assigned_to.remove(user)
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def update_team_users(team_id, org_id=None):
|
||||
"""this function updates assigned_to field on all models when a team is updated"""
|
||||
# Set RLS context for org-scoped queries
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.template.loader import render_to_string
|
||||
|
|
@ -7,10 +7,8 @@ from common.models import Profile
|
|||
from common.tasks import set_rls_context
|
||||
from contacts.models import Contact
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_assigned_user(recipients, contact_id, org_id):
|
||||
"""Send Mail To Users When they are assigned to a contact"""
|
||||
set_rls_context(org_id)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
|
|||
|
||||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks()
|
||||
app.autodiscover_tasks(related_name="celery_tasks") # tasks app uses celery_tasks.py
|
||||
|
||||
# Celery Beat Schedule for recurring tasks
|
||||
app.conf.beat_schedule = {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||
load_dotenv()
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ["SECRET_KEY"]
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-dev-key-please-change-in-production")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.environ.get("DEBUG", "False").lower() == "true"
|
||||
|
|
@ -86,11 +86,11 @@ WSGI_APPLICATION = "crm.wsgi.application"
|
|||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.environ["DBNAME"],
|
||||
"USER": os.environ["DBUSER"],
|
||||
"PASSWORD": os.environ["DBPASSWORD"],
|
||||
"HOST": os.environ["DBHOST"],
|
||||
"PORT": os.environ["DBPORT"],
|
||||
"NAME": os.environ.get("DBNAME", "crm_db"),
|
||||
"USER": os.environ.get("DBUSER", "postgres"),
|
||||
"PASSWORD": os.environ.get("DBPASSWORD", "postgres"),
|
||||
"HOST": os.environ.get("DBHOST", "localhost"),
|
||||
"PORT": os.environ.get("DBPORT", "5432"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,20 +131,20 @@ STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
|
|||
STATIC_URL = "/static/"
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||
|
||||
ENV_TYPE = os.environ["ENV_TYPE"]
|
||||
ENV_TYPE = os.environ.get("ENV_TYPE", "dev")
|
||||
if ENV_TYPE == "dev":
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
MEDIA_URL = "/media/"
|
||||
elif ENV_TYPE == "prod":
|
||||
from .server_settings import *
|
||||
|
||||
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
|
||||
ADMIN_EMAIL = os.environ["ADMIN_EMAIL"]
|
||||
DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "noreply@localhost")
|
||||
ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL", "admin@localhost")
|
||||
|
||||
|
||||
# celery Tasks
|
||||
CELERY_BROKER_URL = os.environ["CELERY_BROKER_URL"]
|
||||
CELERY_RESULT_BACKEND = os.environ["CELERY_RESULT_BACKEND"]
|
||||
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0")
|
||||
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379/0")
|
||||
|
||||
|
||||
LOGGING = {
|
||||
|
|
@ -306,7 +306,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
# STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
|
||||
DOMAIN_NAME = os.getenv("DOMAIN_NAME")
|
||||
DOMAIN_NAME = os.environ.get("DOMAIN_NAME", "http://localhost:8000")
|
||||
|
||||
|
||||
SIMPLE_JWT = {
|
||||
|
|
@ -330,8 +330,8 @@ SIMPLE_JWT = {
|
|||
JWT_ALGO = "HS256"
|
||||
|
||||
|
||||
DOMAIN_NAME = os.environ["DOMAIN_NAME"]
|
||||
SWAGGER_ROOT_URL = os.environ["SWAGGER_ROOT_URL"]
|
||||
DOMAIN_NAME = os.environ.get("DOMAIN_NAME", "http://localhost:8000")
|
||||
SWAGGER_ROOT_URL = os.environ.get("SWAGGER_ROOT_URL", "http://localhost:8000")
|
||||
|
||||
# Google OAuth Configuration
|
||||
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import re
|
||||
|
||||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.db.models import Q
|
||||
|
|
@ -11,15 +11,13 @@ from common.models import Org, Profile
|
|||
from common.tasks import set_rls_context
|
||||
from leads.models import Lead
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
def get_rendered_html(template_name, context={}):
|
||||
html_content = render_to_string(template_name, context)
|
||||
return html_content
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email(
|
||||
subject,
|
||||
html_content,
|
||||
|
|
@ -45,7 +43,7 @@ def send_email(
|
|||
email.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_lead_assigned_emails(lead_id, new_assigned_to_list, site_address, org_id):
|
||||
set_rls_context(org_id)
|
||||
lead_instance = Lead.objects.filter(
|
||||
|
|
@ -76,7 +74,7 @@ def send_lead_assigned_emails(lead_id, new_assigned_to_list, site_address, org_i
|
|||
send_email.delay(**mail_kwargs)
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_assigned_user(recipients, lead_id, org_id, source=""):
|
||||
"""Send Mail To Users When they are assigned to a lead"""
|
||||
set_rls_context(org_id)
|
||||
|
|
@ -102,7 +100,7 @@ def send_email_to_assigned_user(recipients, lead_id, org_id, source=""):
|
|||
msg.send()
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def create_lead_from_file(validated_rows, invalid_rows, user_id, source, company_id):
|
||||
"""Parameters : validated_rows, invalid_rows, user_id.
|
||||
This function is used to create leads from a given file.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.template.loader import render_to_string
|
||||
|
|
@ -7,10 +7,8 @@ from common.models import Profile
|
|||
from common.tasks import set_rls_context
|
||||
from opportunity.models import Opportunity
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email_to_assigned_user(recipients, opportunity_id, org_id):
|
||||
"""Send Mail To Users When they are assigned to an opportunity"""
|
||||
set_rls_context(org_id)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from celery import Celery
|
||||
from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.shortcuts import reverse
|
||||
|
|
@ -10,10 +10,8 @@ from common.tasks import set_rls_context
|
|||
from contacts.models import Contact
|
||||
from tasks.models import Task
|
||||
|
||||
app = Celery("redis://")
|
||||
|
||||
|
||||
@app.task
|
||||
@shared_task
|
||||
def send_email(
|
||||
task_id, recipients, org_id, domain="demo.django-crm.io", protocol="http"
|
||||
):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,20 @@
|
|||
set -e
|
||||
|
||||
echo "Waiting for PostgreSQL..."
|
||||
retries=0
|
||||
max_retries=30
|
||||
while ! python -c "
|
||||
import socket, os
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((os.environ['DBHOST'], int(os.environ['DBPORT'])))
|
||||
s.close()
|
||||
" 2>/dev/null; do
|
||||
retries=$((retries + 1))
|
||||
if [ "$retries" -ge "$max_retries" ]; then
|
||||
echo "ERROR: Could not connect to PostgreSQL after $max_retries attempts."
|
||||
exit 1
|
||||
fi
|
||||
echo " PostgreSQL not ready yet (attempt $retries/$max_retries)..."
|
||||
sleep 1
|
||||
done
|
||||
echo "PostgreSQL is ready."
|
||||
|
|
@ -15,6 +23,9 @@ echo "PostgreSQL is ready."
|
|||
echo "Running migrations..."
|
||||
python manage.py migrate --noinput
|
||||
|
||||
echo "Creating default admin user (if needed)..."
|
||||
python manage.py create_default_admin
|
||||
|
||||
echo "Collecting static files..."
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue