Post, Event, and Image are separate models — not a unified Post model
Circle.so uses a single Post model with post_type (basic|event|image) and conditional attributes (event_settings_attributes for events, gallery for images). We chose separate models — Post, Event, Image — each in its own table with only its relevant fields.
Considered Options
- Unified Post (Circle.so model): Single table with nullable columns. Easier to add a "recent activity" feed that mixes types. Harder to validate type-specific constraints at the database level. Event-specific fields (e.g.
starts_at) would be nullable on every row. - Separate models (chosen): Each model has only the fields it needs, with database-enforced NOT NULL constraints where appropriate. Event-specific validation lives in
Event.clean(), not in conditional logic on Post.
Why separate was chosen
Separate models give stronger schema guarantees. An Event has starts_at and ends_at as required fields — in a unified Post model they'd be nullable, and any post_type=basic row would have nulls in event columns. The ORM makes cross-type queries harder (need GenericForeignKey or unions), but in practice the app rarely needs to query across types — you browse posts in a basic space, events in an event space, images in an image space.
Consequences
- Adding a new content type (e.g. polls, audio) means a new model, views, serializer, URL routing, and tests — not just a new
post_typevalue. This is more upfront work but produces more explicit, self-documenting code. - Cross-type features like "recent activity feed" require querying three tables and merging results, which is less efficient than a single query on Post.