Skip to content

Sales App

A CRM module for managing the full sales pipeline — from lead capture through account management and opportunity tracking — with built-in Metadata API integration for customizable list views and detail pages.

Key Features

  • Lead Management — Capture and qualify leads with flexible metadata fields (phone, company, status, message)
  • Accounts & Contacts — Organize customers into accounts with linked contacts and ownership tracking
  • Opportunity Pipeline — Track deals with configurable stages, amounts, close dates, and win/loss status
  • Metadata API Integration — All models are registered with the Metadata API for auto-generated list views, detail pages, and bulk actions
  • Ownership & Scoping — Every record has an owner field and a scoped manager inherited from BaseModel
  • Flexible Metadata — JSONField on every model allows storing custom fields without schema changes

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Your Application                       │
│                  (app/sales/metadata.py)                     │
└─────────────────────────────────────────────────────────────┘
                    Metadata API Registration
┌─────────────────────────────────────────────────────────────┐
│                     Sales Models                            │
│                  (grit/sales/models.py)                      │
│                                                             │
│   ┌──────────┐    converts to    ┌───────────────────┐      │
│   │   Lead   │ ───────────────▶  │  Contact + Account │     │
│   └──────────┘                   └───────────────────┘      │
│                                          │                  │
│                                     has many                │
│                                          │                  │
│                                          ▼                  │
│                                  ┌───────────────┐          │
│                                  │  Opportunity   │          │
│                                  │  (Deal/Pipeline)│         │
│                                  └───────────────┘          │
└─────────────────────────────────────────────────────────────┘
                          inherits
┌─────────────────────────────────────────────────────────────┐
│                       BaseModel                             │
│   • UUID primary key    • metadata (JSON)                   │
│   • created_at / updated_at    • owner (FK to User)         │
│   • scoped manager                                          │
└─────────────────────────────────────────────────────────────┘

Quick Start

1. Register Models with the Metadata API

In your app/sales/metadata.py, register each model to enable auto-generated list and detail views:

from grit.core.metadata import metadata
from grit.sales.models import Lead, Account, Contact, Opportunity


@metadata.register(Lead)
class LeadMetadata(metadata.ModelMetadata):
    list_display = ('name', 'email', 'message', 'created_at')
    list_actions = [
        [{'label': 'New Lead', 'action': 'new'}]
    ]
    fieldsets = (
        ('Basic Information', {
            'fields': ('name', 'email', 'message', 'created_at')
        }),
    )

2. Configure the Admin Panel

In your app/sales/admin.py, register models with the Django admin for back-office management:

from django.contrib import admin
from grit.sales.models import Lead

@admin.register(Lead)
class LeadAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email', 'created_at')
    search_fields = ('first_name', 'last_name', 'email')
    ordering = ('-created_at',)

3. Create Leads Programmatically

Use the built-in LeadManager to create leads with metadata:

from grit.sales.models import Lead

lead = Lead.objects.create_with_metadata(
    first_name="Jane",
    last_name="Doe",
    email="jane@example.com",
    phone="+1-555-0123",
    company="Acme Corp",
    message="Interested in enterprise plan"
)

Core Models

Lead

Represents a potential customer who has expressed interest. Leads are the entry point of the sales pipeline.

Field Type Description
first_name CharField Lead's first name
last_name CharField Lead's last name
email EmailField Lead's email address

Metadata Fields (stored in the metadata JSONField):

Key Type Description
phone string Phone number
company string Company name
status object Status with label and value (e.g., "New", "Unqualified")
message string Message from the lead

Custom Manager:

# LeadManager provides create_with_metadata() for structured lead creation
lead = Lead.objects.create_with_metadata(
    first_name="Jane",
    last_name="Doe",
    email="jane@example.com",
    phone="+1-555-0123",
    company="Acme Corp"
)

Contact

Represents a qualified person associated with an account. Contacts can optionally be linked to a user in the system.

Field Type Description
first_name CharField Contact's first name
last_name CharField Contact's last name
title CharField Job title
email EmailField Contact email
user OneToOneField Optional link to a system user
account ForeignKey Associated account

Account

Represents a company or organization. Accounts group related contacts and opportunities together.

Field Type Description
name CharField Account/company name

Related Objects: - contacts — All contacts linked to this account - opportunities — All opportunities linked to this account

Opportunity

Represents a potential deal in the sales pipeline. Opportunities track revenue, stage progression, and close dates.

Field Type Description
name CharField Opportunity name
account ForeignKey Associated account
amount DecimalField Deal value
close_date DateField Expected close date
stage CharField Pipeline stage (configurable via settings)
is_closed BooleanField Whether the deal is closed
is_won BooleanField Whether the deal was won

Metadata API Configuration

List Views

Define filtered views for list pages using list_views. Each view specifies which fields to display and what filters to apply:

@metadata.register(Opportunity)
class OpportunityMetadata(metadata.ModelMetadata):
    list_display = ('name', 'account', 'stage', 'amount', 'close_date')
    list_views = {
        'all': {
            'label': 'All',
            'fields': ('name', 'email', 'message', 'created_at'),
            'filters': {}
        },
        'new': {
            'label': 'New',
            'fields': ('name', 'email', 'created_at'),
            'filters': {
                'stage': 'new'
            }
        }
    }

Inlines

Display related records on detail pages using Django admin-style inlines:

from django.contrib import admin

class ContactInline(admin.TabularInline):
    model = Contact
    extra = 0
    fields = ('first_name', 'last_name', 'email')

@metadata.register(Account)
class AccountMetadata(metadata.ModelMetadata):
    list_display = ('name', 'owner')
    inlines = [ContactInline, OpportunityInline]

Configuration Reference

Opportunity Stages

Opportunity stages are configurable via APP_METADATA_SETTINGS in your Django settings:

# settings.py
APP_METADATA_SETTINGS = {
    'CHOICES': {
        'opportunity_stages': {
            'new': {'label': 'New'},
            'qualification': {'label': 'Qualification'},
            'proposal': {'label': 'Proposal'},
            'negotiation': {'label': 'Negotiation'},
            'closed_won': {'label': 'Closed Won'},
            'closed_lost': {'label': 'Closed Lost'},
        }
    }
}

The first stage in the dictionary is used as the default value for new opportunities.

BaseModel Inherited Fields

All sales models inherit these fields from BaseModel:

Field Type Description
id UUIDField Auto-generated unique identifier
metadata JSONField Flexible JSON storage for custom fields
created_at DateTimeField Auto-set on creation
updated_at DateTimeField Auto-set on save
owner ForeignKey Record owner (links to user)