This guide provides step-by-step instructions to set up, run, and execute the advertising system.
Ensure you have the following installed:
- Python 3.8+
- Django 4+
- PostgreSQL (Recommended, but SQLite can be used for testing)
- Redis (For Celery task queue)
- Celery (For scheduled background tasks)
- Celery Beat (For periodic tasks)
git clone git@github.com:Amir-4m/adTest.git
cd adTest
python -m venv venv
source venv/bin/activate # On Mac/Linux
venv\Scripts\activate # On Windows
pip install -r requirements.txt
DB_NAME=adtest
DB_USER=postgres
DB_PASSWORD=password
DB_ENGINE=django.db.backends.postgresql_psycopg2
DB_HOST=localhost
DB_PORT=5432
DEBUG=True
SECRET_KEY="397@^%w%c2nji+rkp62u#n1!8b%4on22b@1o*w-zh=0_x2x&5e"
REDIS_URL=redis://localhost:6379/
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Visit http://127.0.0.1:8000/admin/ to access the Django admin panel.
Celery is required for background tasks like enforcing budgets and handling dayparting.
redis-server # On Mac/Linux
redis-server.exe # On Windows (if installed)
celery -A adTest worker --loglevel=info
celery -A adTest beat --loglevel=info
python manage.py test apps/*
Attributes:
- Default cost per click
- Default cost per 1000 impressions
- Default cost per view
- Default cost per acquisition
Purpose: Provide fallback pricing values if an individual ad does not define its own.
Attributes:
- Name
- Daily budget
- Monthly budget
- Timezone (as a string)
- Owner (user reference)
- Active flag
Methods:
-
get_daily_spend()
- Aggregate all cost transactions for the current day using the brand’s timezone (by converting transaction timestamps to the brand’s local time).
-
get_monthly_spend()
- Aggregate all cost transactions for the current month, again using the brand’s timezone.
Attributes:
- Reference to a Brand
- Name
- Status (values:
DRAFT
,SCHEDULED
,RUNNING
,PAUSED
,BUDGET_REACHED
,COMPLETED
) - Allowed start and end times (for dayparting, stored in UTC)
Methods:
start()
→ Set status toRUNNING
pause()
→ Set status toPAUSED
budget_reach()
→ Set status toBUDGET_REACHED
schedule()
→ Set status toSCHEDULED
complete()
→ Set status toCOMPLETED
Attributes:
- Belongs to a Campaign
- Name
- Active flag
Attributes:
- Belongs to an AdSet
- Name
- Active flag
- Media file and text content
- Cost parameters (
click
,impression
,view
,acquisition
)
Methods:
-
get_effective_cost()
- Return the ad’s own cost if set; otherwise, fallback to GlobalAdPricing.
-
log_event(event_type)
- When a user interaction occurs (
click
,impression
,view
, oracquisition
), calculate the cost based on the event type and create a cost transaction. - After logging the transaction, aggregate the brand’s current spending.
- If the brand’s spending exceeds its daily or monthly budget, update all active campaigns for that brand to
a
Budget Reached
state.
- When a user interaction occurs (
Attributes:
- References to Brand, Campaign, and optionally Ad
- Amount charged
- Transaction type (e.g.,
COST
orPAYMENT
) - Cost type (e.g.,
CLICK
,IMPRESSION
,VIEW
,ACQUISITION
) - Timestamp (with creation time, later converted to brand local time for filtering)
When an ad event occurs:
- Determine the event type (
click
,impression
,view
, oracquisition
). - Calculate the cost using the ad’s defined cost or the global default.
- Create a Transaction record for the cost event.
- Aggregate the brand’s spending for the day and month by converting each transaction’s timestamp into the brand’s local time.
- If spending exceeds the brand’s daily or monthly budget, update the status of all currently running campaigns under that brand to “Budget Reached.”
Periodically (e.g., every 5 minutes), for each brand that has active campaigns:
- Use the brand methods to calculate daily and monthly spending.
- If spending is over budget, mark running campaigns as “Budget Reached.”
- If spending is under budget, reset campaigns from “Budget Reached” back to “Scheduled.”
Periodically scan campaigns with a “Scheduled” status:
- For campaigns with allowed start/end times (stored in UTC), combine the current UTC date with these times.
- Convert these allowed times to the brand’s local timezone.
- If the current local time falls within the allowed window, update the campaign’s status to “Running.”
- For campaigns without dayparting settings, simply set them to “Running.”
Periodically scan campaigns that are “Running” and use dayparting:
- Convert the campaign’s allowed start/end times (combined with the current UTC date) into the **brand’s local time **.
- If the current local time falls outside the allowed window, update the campaign’s status to “Scheduled” (or a paused state).
- At the start of a new day or new month, spending totals are recalculated naturally via the aggregation methods in the Brand model.
- Campaigns may then be re-enabled if the new aggregated spend is below the budget thresholds.
DATA STRUCTURES:
GlobalAdPricing: { cost_per_click, cost_per_impression, cost_per_view, cost_per_acquisition }
Brand: { name, daily_budget, monthly_budget, timezone, owner, is_active }
Campaign: { brand, name, status, allowed_start_hour, allowed_end_hour }
AdSet: { campaign, name, is_active }
Ad: { adset, name, is_active, media_file, content, cost parameters }
Transaction: { brand, campaign, ad, amount, transaction_type, cost_type, created_at }
WORKFLOW:
FUNCTION log_ad_event(ad, event_type):
cost = ad.get_effective_cost(event_type)
CREATE Transaction with cost, event_type, and timestamp
brand = ad.adset.campaign.brand
daily_spend = brand.get_daily_spend()
monthly_spend = brand.get_monthly_spend()
IF daily_spend OR monthly_spend exceeds budget:
UPDATE all running campaigns of brand -> status = BUDGET_REACHED
ENDIF
TASK enforce_brand_budget:
FOR each brand with active campaigns:
daily_spend = brand.get_daily_spend()
monthly_spend = brand.get_monthly_spend()
IF daily_spend OR monthly_spend exceeds budget:
UPDATE running campaigns -> status = BUDGET_REACHED
ELSE:
UPDATE campaigns with status BUDGET_REACHED -> status = SCHEDULED
ENDIF
ENDFOR
TASK start_scheduled_campaigns:
FOR each campaign with status SCHEDULED:
IF campaign has dayparting settings:
allowed_start = Convert(UTC_date + allowed_start_hour) to brand local time
allowed_end = Convert(UTC_date + allowed_end_hour) to brand local time
IF allowed period spans midnight:
Adjust allowed_end by adding 1 day
ENDIF
IF current brand local time is within allowed period:
UPDATE campaign -> status = RUNNING
ENDIF
ELSE:
UPDATE campaign -> status = RUNNING
ENDIF
ENDFOR
TASK stop_dayparting_campaigns:
FOR each campaign with status RUNNING and dayparting settings:
Convert allowed times to brand local time (handle midnight crossing)
IF current brand local time is outside allowed period:
UPDATE campaign -> status = SCHEDULED
ENDIF
ENDFOR
DAILY/MONTHLY RESET:
(Implicit via recalculation in brand.get_daily_spend() and get_monthly_spend())
Campaigns are re-enabled if spending falls below budget thresholds.