Demo data and packs¶
BottleCRM has two independent ways to populate an organization with non-empty data: the
seed_data management command, which generates a large, randomized dataset for an entire org, and
vertical packs, which apply a small, curated, industry-specific set of pipelines and sample
records to an org that already exists.
Seeding a demo organization¶
Run from backend/ (or docker compose exec backend python manage.py seed_data --email
you@example.com against the Docker stack). This is the command referenced in
Docker quick start and First sign-in.
The command (backend/common/management/commands/seed_data.py) creates an organization and fills
it with realistic-looking fictional data, contacts, accounts, leads, opportunities, cases, tasks,
products, invoices, estimates, recurring invoices, sales goals, teams and tags, using curated
name/company/address pools and reserved demo domains (example.com, .example) so nothing it
generates resembles a real person or business. The first organization it creates is always
named MicroPyramid, specifically so local-dev workflows have a stable, known org name to
sign in against with devlogin --org MicroPyramid. An admin profile is created for whichever
--email you pass. Re-running the command with the same email reuses the existing org and user
rather than creating duplicates. It's safe to run more than once.
Counts for every entity type (--leads, --accounts, --opportunities, --invoices, and so on)
and locale (--currency, --country) are configurable flags; see --help for the full list. A
--clear flag deletes existing CRM data for re-seeding (but never users, orgs or profiles): it's
a separate, broader operation from the sample-data clearing described below, and is not scoped to
records a pack created.
Vertical packs¶
A vertical pack is a small, declarative JSON manifest, not a data generator, that shapes an org toward one industry: a starter pipeline (with stages) for leads, cases and tasks; a handful of custom field definitions; tags; products; some terminology overrides (for example relabeling "Lead" as "Enquiry"); and a short list of named sample records (accounts, contacts, deals, tickets, leads, tasks) that reference each other by name within the manifest.
Four packs ship in backend/packs/, one manifest file per pack:
| Pack id | File | Description |
|---|---|---|
real-estate |
real-estate.json |
Brokers, agencies and developers selling residential and commercial property. |
professional-services |
professional-services.json |
Agencies, consultancies and accounting firms selling billable work. |
education-admissions |
education-admissions.json |
Coaching centres, training institutes and colleges managing admissions. |
events-weddings |
events-weddings.json |
Wedding planners, event agencies and venues taking enquiries through to a delivered event. |
Every manifest is validated strictly at load time (backend/common/packs/schema.py). An unknown
key, an invalid stage_type, a sample_data reference to an account or stage name that isn't
declared elsewhere in the same pack, and similar mistakes all fail validation rather than being
silently ignored or half-applied.
Applying a pack¶
Applying a pack is handled by backend/common/packs/applier.py's apply_pack(), reachable
through:
- The UI: the Settings → Organization page (for an existing org) and the organization creation page (for a brand-new org) both offer a pack picker, calling the same backend endpoint.
- The API:
POST /api/packs/<pack_id>/apply/, restricted to an organization admin (request.profile.role == "ADMIN"); any authenticated member can list available packs viaGET /api/packs/, but only an admin can apply one.
Applying a pack is strictly additive. The applier's own description of itself: it creates
what's missing and skips what already exists, and it "never calls .update(), never calls
.save() on a row it did not just construct, and never deletes." A pipeline whose name already
exists in the org is skipped entirely (along with its stages); a custom field, tag or product that
already exists by its matching key is skipped individually. This makes applying the same pack
twice, or applying a second pack to an org that already has one, safe: it can only add what's
still missing, never overwrite or remove something you've since changed. The one exception is
terminology and the org's vertical label, which are merged in (only keys not already set are
added) rather than duplicated.
Sample records created by a pack (accounts, contacts, deals/opportunities, tickets/cases, leads
and tasks) are marked with an is_sample boolean field, set only by the applier. It is
read_only on every serializer that exposes it, so it can never be set by a client request. Every
sample row is also tagged with a "Sample data" tag and assigned to whichever admin applied the
pack, purely for visibility in the UI; the tag carries no special authority and isn't what the
system uses to know a row is sample data.
If an org already has any sample rows (from this pack or a previous one), applying sample_data
again is skipped. A pack's sample data is only ever created once per org, regardless of how many
times you re-apply.
Clearing sample data¶
Only clear_sample_data() deletes sample records, via DELETE /api/packs/sample-data/
(also admin-only, also reachable from the Settings → Organization page). It is the sole
counterpart to pack-applied sample data: nothing else in the system deletes rows just because
is_sample is set.
Clearing is scoped strictly to rows where is_sample=True for the calling org, deleted in an
order that removes child records (tasks, leads) before the parents they can reference (tickets,
deals, contacts, accounts). It never removes anything else. A real record you created yourself is
untouched even if it happens to reference a sample record, and a sample row is retained rather
than deleted if a non-sample record still depends on it (for example, a real invoice raised
against a demo account). Retained rows are reported back, not silently skipped, so the caller
knows some sample data was intentionally kept rather than assuming the clear was incomplete.