Data Models#

The Django application is split into domain-specific apps. 35 models span 10 apps — the diagrams below show how they relate.

Key apps#

App

Models

Purpose

housing

7

Neighbourhoods, districts, wards, property snapshots, safety snapshots

products

7

Blog posts, itineraries, categories, favourites, preferences

payments

7

Subscription tiers, invoices, Stripe events, identity audit log

reports

5

Crime data purchases, datasets, access grants

travel_quiz

6

Quiz definitions, questions, neighbourhood scoring, results

things_to_do

2

Activity categories and articles

comments

1

Threaded blog post comments

ebook

1

eBook purchase tracking

cookieconsent

1

GDPR cookie consent

Full entity-relationship diagram#

Housing & Safety domain#

erDiagram District { int id PK string slug UK string name string description string color string icon int order bool is_active } Neighbourhood { int id PK string slug UK string name int district_id FK string vibe string best_for decimal latitude decimal longitude decimal safety_score string safety_level decimal violent_crime_score decimal property_crime_score decimal gang_safety_score int avg_rent_1br_min int avg_rent_1br_max int avg_property_price int price_per_sqm int airbnb_listings int airbnb_avg_nightly bool is_active date metrics_as_of datetime updated_at } NeighbourhoodHighlight { int id PK int neighbourhood_id FK string icon string label string value int order } PropertySnapshot { int id PK int neighbourhood_id FK date data_date int avg_rent_1br int avg_rent_2br int avg_property_price int airbnb_listings string source } Ward { int id PK int number UK string name json neighborhoods_text bool is_active } WardSafetySnapshot { int id PK int ward_id FK string period_label date period_start date period_end string source_version decimal overall_score decimal violent_crime_score decimal property_crime_score decimal gang_safety_score decimal burglary_score decimal vehicle_crime_score string trend_direction bool is_latest datetime computed_at } NeighbourhoodWard { int id PK int neighbourhood_id FK int ward_id FK decimal weight date effective_from date effective_to } District ||--o{ Neighbourhood : contains Neighbourhood ||--o{ NeighbourhoodHighlight : highlights Neighbourhood ||--o{ PropertySnapshot : tracks Neighbourhood ||--o{ NeighbourhoodWard : maps_to Ward ||--o{ NeighbourhoodWard : maps_to Ward ||--o{ WardSafetySnapshot : has

Content & Commerce domain#

erDiagram Category { int id PK string name UK string slug_category UK string image_url bool show_on_home int order } BlogPost { int id PK string identifier UK string title markdown content string access float safety int category_id FK int views float rating string tags datetime created_at datetime updated_at } Itinerary { int id PK string itineraryID UK string name int days string access int user_id FK int views float rating string travel_budget datetime created_at } Comment { int id PK int blog_post_id FK int user_id FK text body int parent_id FK bool active datetime created_at } Favorite { int id PK string access int user_id FK int blog_post_id FK int itinerary_id FK datetime created_at } TravelMode { int id PK string mode UK } Category ||--o{ BlogPost : categorises BlogPost ||--o{ Comment : has Comment ||--o{ Comment : replies BlogPost ||--o{ Favorite : saved_as Itinerary ||--o{ Favorite : saved_as Itinerary }o--o{ TravelMode : uses

Payments & Subscriptions domain#

erDiagram SubscriptionTier { int id PK string tier_id UK string name text description decimal price_eur string stripe_price_id_test string stripe_price_id_live bool includes_data bool includes_reading bool includes_ebooks bool is_active int sort_order } Customer { int id PK int user_id FK string customer_id string email } Subscription { int id PK int customer_id FK int user_id FK string email string session_id string subscription_id decimal amount string currency string status int tier_id FK datetime started_at datetime ended_at datetime canceled_at } GuestSubscription { int id PK string email int tier_id FK string status string stripe_subscription_id string access_token decimal amount string currency datetime activated_at datetime expires_at } Invoice { int id PK string invoice_number string customer_email decimal amount string currency string stripe_session_id file pdf_file bool email_sent bool lexoffice_uploaded } StripeCheckoutEvent { int id PK string event_id UK string event_type bool success text error_message datetime processed_at } SubscriptionTier ||--o{ Subscription : defines SubscriptionTier ||--o{ GuestSubscription : defines Customer ||--o{ Subscription : owns

Reports & Datasets domain#

erDiagram Dataset { int id PK string slug UK string name string category string status string region string data_bucket string data_path string stripe_product_id_test string stripe_price_id_test string stripe_product_id_live string stripe_price_id_live string current_version bool workflow_enabled } DatasetAccess { int id PK int user_id FK string email int dataset_id FK string status string access_token string stripe_session_id decimal amount datetime paid_at datetime expires_at int legacy_purchase_id FK } CrimeDataPurchase { int id PK int user_id FK string email string access_type string neighborhood_slug string status string access_token decimal amount datetime paid_at datetime expires_at } Dataset ||--o{ DatasetAccess : grants CrimeDataPurchase ||--o{ DatasetAccess : migrated_to

Travel Quiz domain#

erDiagram Area { int id PK string name string slug UK string color int order } QuizNeighbourhood { int id PK string name string slug UK int area_id FK string vibe int walkability int safety string wind int score_ocean int score_mountain int score_luxury int score_budget int score_nightlife int score_surf bool is_active } Quiz { int id PK string slug UK string title text subtitle bool is_active int order } Question { int id PK int quiz_id FK text text string question_type string score_attribute_high string score_attribute_low int order } QuestionChoice { int id PK int question_id FK string text int value string score_attribute int order } QuizResult { int id PK int quiz_id FK int user_id FK json answers int top_neighbourhood_id FK int second_neighbourhood_id FK int third_neighbourhood_id FK json scores datetime created_at } Area ||--o{ QuizNeighbourhood : groups Quiz ||--o{ Question : contains Question ||--o{ QuestionChoice : has Quiz ||--o{ QuizResult : produces QuizNeighbourhood ||--o{ QuizResult : recommended_in

Corrections from prior diagram#

The following errors were present in the earlier visual and have been fixed:

Entity

Error

Correction

PropertySnapshot

Field shown as captured_at

Actual field is data_date

NeighbourhoodHighlight

Fields shown as title, description

Actual fields are label, value

Ward

Field shown as code

Actual field is number

Comment

Missing parent, active, user fields

All three are present on the model

Favorite

Missing itinerary FK

Has both blog_post and itinerary FKs

NeighbourhoodWard

Missing weight, effective_from, effective_to

All three are present

Subscription

Only showed subscription_tier_id, user_id, started_at, ends_at

Has 15 fields including email, status, tier, amount, etc.

See the Auto-generated Reference section for per-model field docs built from data/models.json.