Campaign e Tracking Links
TLDR: cria as tabelas
campaignetracking_linkspara gerenciar campanhas de marketing e seus links de rastreamento (UTM), com múltiplos links por campanha.
Status: proposed Created: 2026-08-07 Owner: @diegoFranciscoo
Context
Marketing precisa cadastrar campanhas e gerar/armazenar links de rastreamento (UTM) associados a cada uma. Uma mesma campanha pode ter vários links. Hoje cada campanha usa um único canal (source_channel), mas isso pode virar múltiplos canais no futuro.
A campanha referencia opcionalmente um Trial.
Decisão de modelagem: source_channel fica em tracking_links
Mesmo com o cenário atual sendo 1 canal por campanha, source_channel fica na tabela tracking_links (e não em campaign):
- É a mesma natureza dos demais campos do link (
utm_source,utm_medium) — atributo do link, não da campanha. - Suporta o futuro multi-canal sem migração: hoje todos os links de uma campanha compartilham o mesmo
source_channele a listagem fazDISTINCT; amanhã passam a divergir naturalmente. - Colocar em
campaignexigiria uma migração dolorosa (mover coluna + migrar dados + reescrever lógica) quando o negócio evoluir.
A listagem de campanhas exibe o(s) canal(is) via agregação (DISTINCT dos source_channel dos links da campanha) — problema de apresentação, não de modelagem.
Objectives
- Criar o app Django
campaignscom os modelsCampaigneTrackingLink. - Registrar o app em
INSTALLED_APPS. - Gerar a migration inicial.
Non-goals
- Registro no Django admin — escopo futuro.
- Endpoints/serializers de API (DRF) — não faz parte deste escopo.
- Tabela intermediária entre usuário trial e campanha (TODO separado no
Trial). - Geração automática de
final_linka partir dos UTMs — o link é persistido como veio; a montagem/validação de UTM fica para escopo futuro. - UI/tela de listagem de campanhas.
Changes
apps/campaigns/ (novo app)
apps/campaigns/__init__.pyapps/campaigns/apps.py—CampaignsConfig(padrão doTrialsConfig).apps/campaigns/models/__init__.py— reexportaCampaign,TrackingLink.apps/campaigns/models/campaign.py— modelCampaign(BaseModel):name—CharField(max_length=255), obrigatório.status—CharFieldcomTextChoices:active(“Ativa”),inactive(“Inativa”),archived(“Arquivada”); defaultinactive.start_date—DateField(null=True, blank=True).end_date—DateField(null=True, blank=True).trial—ForeignKey("trials.Trial", on_delete=SET_NULL, null=True, blank=True, related_name="campaigns").slug—SlugField(max_length=255, unique=True), obrigatório. Auto-gerado a partir denamequando vazio, seguindo o padrão_generate_unique_slugdoCourse(checa contraall_objectspara não colidir com soft-deleted).Meta:verbose_name/plural,ordering = ["-created_at"].clean(): validaend_date > start_datequando ambos preenchidos (padrão doTrial).
apps/campaigns/models/tracking_link.py— modelTrackingLink(BaseModel):campaign—ForeignKey(Campaign, on_delete=CASCADE, related_name="tracking_links"), obrigatório.source_channel—CharField(max_length=100, blank=True).utm_source—CharField(max_length=255), obrigatório.utm_medium—CharField(max_length=255), obrigatório.utm_content—CharField(max_length=255, blank=True).utm_term—CharField(max_length=255, blank=True).final_link—URLField(max_length=2048), obrigatório.Meta:verbose_name/plural,ordering = ["-created_at"].
apps/campaigns/migrations/__init__.pyapps/campaigns/migrations/0001_initial.py— gerada viamakemigrations(depende detrials).
config/settings/base.py
- Adicionar
"apps.campaigns.apps.CampaignsConfig"emINSTALLED_APPS, junto aos demais apps locais.
How to verify
python manage.py makemigrations campaignsgera0001_initialsem prompts inesperados.python manage.py migrateaplica sem erro.python manage.py checkpassa.- No shell: criar uma
Campaignsemsluggera slug único a partir doname; criar duas com o mesmonamegera slugs distintos. - Criar duas
TrackingLinkpara a mesmaCampaignfunciona (múltiplos links por campanha). end_date <= start_datelevantaValidationErrornofull_clean().
Documentation
- Sem novas regras de negócio a documentar em
.project/docs/além do próprio spec. Caso seeds/factories sejam necessários, aplicarwehive:seedem escopo posterior.