Django SaaS Boilerplate

repository·main·Indexed 42 days ago

https://github.com/eriktaveras/django-saas-boilerplate

A lightweight, production-ready Django starter kit for building SaaS applications. It features email-only authentication via CustomUser, Stripe subscription integration, a user dashboard, and background tasks using Django 6.0's native @task decorator. Includes built-in Content Security Policy (CSP) middleware with nonce support and deployment guides for Heroku and VPS.

Tokens
6.4K
Snippets
23
Records
33
Agent score
54%

What's inside Django SaaS Boilerplate

  1. Quick start with Django SaaS Boilerplate

    main

    To get a local development environment running, ensure you have Python 3.12+ installed. On Debian or Ubuntu, you may need to install the venv package first:

    sudo apt install python3.12-venv

    Follow these steps to clone, install, and run the project:

    1. Clone the repository.
    2. Run make install to create a virtualenv and install dependencies.
    3. Copy .env.example to .env and configure your environment variables.
    4. Run make migrate to set up the database.
    5. Run make seed to populate demo data (this will print an admin password to your terminal).
    6. Run make run to start the development server.

    Access the application at http://localhost:8000.

    git clone https://github.com/eriktaveras/django-saas-boilerplate.git
    cd django-saas-boilerplate
    make install
    cp .env.example .env
    make migrate
    make seed
    make run
  2. Deploy to Heroku

    main

    To deploy to Heroku, create your app and set the required environment variables, then push your code. Note that DEBUG must be set to False in production.

    heroku create your-app-name
    heroku config:set SECRET_KEY=your-secret-key
    heroku config:set DEBUG=False
    git push heroku main
    heroku run python manage.py migrate
  3. Deploy to a VPS

    main

    When deploying to a VPS, use Gunicorn to serve the application. It is recommended to put a reverse proxy like Nginx or Caddy in front to handle TLS termination. The application uses the X-Forwarded-Proto header to determine if a request is secure.

    Warning: Do not bind Gunicorn directly to 0.0.0.0 without a proxy, as clients could spoof the header and bypass SECURE_SSL_REDIRECT.

    pip install -r requirements.txt
    python manage.py migrate
    python manage.py collectstatic --noinput
    gunicorn core.wsgi --bind 127.0.0.1:8000
  4. Customize subscription plans and logic

    main

    You can extend the subscription functionality in several ways:

    • Subscription Plans: Modify plans directly in the Stripe Dashboard, update the STRIPE_PRICE_ID in your .env file, and customize plan features within models.py.
    • UI Customization: Edit templates/subscriptions/subscription.html for layout changes and modify your Tailwind CSS configuration for styling.
    • Business Logic: Extend views.py for custom subscription workflows, modify webhooks.py to handle different Stripe events, and add custom validation in models.py.
  5. Set up the Stripe Subscription System

    main

    To integrate the Stripe subscription system, follow these four steps:

    1. Stripe Account Setup: Obtain your Publishable and Secret keys from the Stripe Dashboard under Developers -> API keys.
    2. Product Configuration: Create a product in the Stripe Dashboard and add a recurring price. Copy the resulting Price ID.
    3. Webhook Setup: In the Stripe Dashboard under Developers -> Webhooks, add an endpoint pointing to https://your-domain.com/subscriptions/webhook/. Subscribe to the customer.subscription.updated event and copy the Webhook Signing Secret.
    4. Environment Configuration: Add the required keys to your .env file.
    STRIPE_PUBLIC_KEY=pk_test_...
    STRIPE_SECRET_KEY=sk_test_...
    STRIPE_WEBHOOK_SECRET=whsec_...
    STRIPE_PRICE_ID=price_...
  6. How subscription access is synchronized between Stripe and the app

    main

    The project uses a dual-record system to manage access. While StripeCustomer tracks Stripe-specific metadata, the application's permission gates (in the dashboard) actually read from apps.dashboard.models.UserSettings.

    To ensure a user who has paid actually receives access, the _sync_access function must be called whenever a subscription status changes. This function maps Stripe's raw statuses to the project's internal status categories and updates the subscription_status and subscription_end_date on the UserSettings model.

  7. Use CustomUser for email-based authentication

    main

    The boilerplate uses a custom user model, CustomUser, which replaces the default Django username field with a unique email field. Authentication is performed using the email address.

    When interacting with the user model via the Django shell or management commands, use the email field as the primary identifier. The CustomUser model is configured with USERNAME_FIELD = 'email' and does not require a username.

    # Example: Creating a user via the Django shell
    from django.contrib.auth import get_user_model
    
    User = get_user_model()
    user = User.objects.create_user(email='user@example.com', password='securepassword')
    print(user.email)
  8. Configure environment variables

    main

    The application is configured via environment variables defined in a .env file. Copy .env.example to .env and set the following keys:

    Required:

    • DEBUG: Boolean (e.g., True)
    • SECRET_KEY: Django secret key

    Database:

    • DATABASE_URL: Connection string (defaults to SQLite if not provided. Example: postgres://user:password@localhost:5432/dbname)

    Stripe (Required for payments):

    • STRIPE_PUBLIC_KEY: Your Stripe public key
    • STRIPE_SECRET_KEY: Your Stripe secret key
    • STRIPE_WEBHOOK_SECRET: Your Stripe webhook signing secret
    • STRIPE_PRICE_ID: The ID of the Stripe price to use

    Email (Optional):

    • EMAIL_BACKEND: Django email backend (defaults to console)
    • EMAIL_HOST: SMTP host
    • EMAIL_PORT: SMTP port
    • EMAIL_HOST_USER: SMTP username
    • EMAIL_HOST_PASSWORD: SMTP password
    # Required
    DEBUG=True
    SECRET_KEY=your-secret-key
    
    # Database (default: SQLite)
    # DATABASE_URL=postgres://user:password@localhost:5432/dbname
    
    # Stripe (required for payments)
    STRIPE_PUBLIC_KEY=pk_test_...
    STRIPE_SECRET_KEY=sk_test_...
    STRIPE_WEBHOOK_SECRET=whsec_...
    STRIPE_PRICE_ID=price_...
  9. Use Django 6.0 native background tasks

    main

    The boilerplate uses Django 6.0's native @task() decorator to handle asynchronous operations (like sending emails) without needing Celery.

    To define a task, use the @task decorator. To execute it asynchronously, call the .enqueue() method on the function.

    from django.tasks import task
    
    @task
    def send_welcome_email(user_email):
        send_mail("Welcome!", "...", None, [user_email])
    
    # In your view, call it like this:
    send_welcome_email.enqueue(user_email=user.email)
  10. Use Django 6.0 Content Security Policy (CSP)

    main

    The project includes built-in CSP middleware with nonce support. You can configure the policy in your settings and use the {{ csp_nonce }} variable in your templates to allow specific scripts.

    # In settings.py
    SECURE_CSP = {
        "script-src": [CSP.SELF, CSP.NONCE],
        "style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
    }
    <!-- In templates -->
    <script nonce="{{ csp_nonce }}" src="..."></script>
  11. Subscription module API reference

    main

    Models

    • Subscription: Tracks subscription status and details.
    • Customer: Stores customer information and Stripe IDs.

    Views

    • subscription_view: The main subscription page.
    • webhook: Endpoint that handles incoming Stripe webhook events.
    • cancel_subscription: Logic for managing subscription cancellations.

    Webhooks

    • handle_subscription_updated: Processes updates to existing subscriptions.
    • handle_payment_succeeded: Handles successful payment events.
    • handle_payment_failed: Manages failed payment events.