This commit is contained in:
Flatlogic Bot 2026-02-06 20:41:24 +00:00
parent e083afc1d5
commit 16066e4ba4
14 changed files with 1091 additions and 95 deletions

View File

@ -10,6 +10,8 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lexend:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
@ -96,19 +98,31 @@
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'mp_list' %}">MPs</a></li>
<li class="nav-item"><a class="nav-link" href="#">Tickers</a></li>
<li class="nav-item"><a class="nav-link" href="/admin/">Admin</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'ticker_list' %}">Tickers</a></li>
{% if user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{% url 'profile' %}">Watchlist</a></li>
{% endif %}
</ul>
<div class="d-flex align-items-center">
{% if user.is_authenticated %}
<span class="text-muted me-3">Hello, {{ user.username }}</span>
<form action="{% url 'logout' %}" method="post" class="d-inline">
<div class="dropdown me-3">
<button class="btn btn-link nav-link dropdown-toggle d-flex align-items-center" type="button" id="userDropdown" data-bs-toggle="dropdown">
<i class="bi bi-person-circle me-2"></i>{{ user.username }}
</button>
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
<li><a class="dropdown-item" href="{% url 'profile' %}"><i class="bi bi-person me-2"></i>Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<form action="{% url 'logout' %}" method="post" class="dropdown-item p-0">
{% csrf_token %}
<button type="submit" class="btn btn-outline-light btn-sm me-2">Logout</button>
<button type="submit" class="btn btn-link dropdown-item py-2 px-3 m-0 border-0"><i class="bi bi-box-arrow-right me-2"></i>Logout</button>
</form>
</li>
</ul>
</div>
{% else %}
<a href="{% url 'login' %}" class="nav-link me-3">Login</a>
<a href="#" class="btn btn-primary-custom">Get Started</a>
<a href="{% url 'signup' %}" class="btn btn-primary-custom">Get Started</a>
{% endif %}
</div>
</div>
@ -126,5 +140,73 @@
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
document.addEventListener('click', function(e) {
const btn = e.target.closest('.follow-btn');
if (btn) {
const mpId = btn.getAttribute('data-mp-id');
const ticker = btn.getAttribute('data-ticker');
{% if not user.is_authenticated %}
window.location.href = "{% url 'login' %}";
return;
{% endif %}
const formData = new FormData();
if (mpId) formData.append('mp_id', mpId);
if (ticker) formData.append('ticker', ticker);
fetch("{% url 'toggle_follow' %}", {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => response.json())
.then(data => {
// Custom event for specific page handling
const event = new CustomEvent('followToggled', { detail: { data, btn } });
document.dispatchEvent(event);
if (data.status === 'followed') {
if (btn.classList.contains('btn-outline-light')) {
btn.innerText = 'Following';
btn.classList.remove('btn-outline-light');
btn.classList.add('btn-success');
}
} else if (data.status === 'unfollowed') {
if (btn.classList.contains('btn-success')) {
btn.innerText = 'Follow';
btn.classList.remove('btn-success');
btn.classList.add('btn-outline-light');
}
}
})
.catch(error => {
console.error('Error toggling follow:', error);
});
}
});
</script>
</body>
</html>

View File

@ -29,66 +29,22 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="h5 mb-0">Live Disclosure Feed</h3>
<div class="dropdown">
<button class="btn btn-dark btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
All Parties
<button class="btn btn-dark btn-sm dropdown-toggle" type="button" id="partyFilterBtn" data-bs-toggle="dropdown">
{% if selected_party %}{{ selected_party }}{% else %}All Parties{% endif %}
</button>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="#">Liberal</a></li>
<li><a class="dropdown-item" href="#">Conservative</a></li>
<li><a class="dropdown-item" href="#">NDP</a></li>
<li><a class="dropdown-item party-filter" href="#" data-party="">All Parties</a></li>
<li><a class="dropdown-item party-filter" href="#" data-party="Liberal">Liberal</a></li>
<li><a class="dropdown-item party-filter" href="#" data-party="Conservative">Conservative</a></li>
<li><a class="dropdown-item party-filter" href="#" data-party="NDP">NDP</a></li>
</ul>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-hover align-middle custom-table">
<thead>
<tr>
<th class="text-muted fw-normal">MP</th>
<th class="text-muted fw-normal">Ticker</th>
<th class="text-muted fw-normal">Type</th>
<th class="text-muted fw-normal">Amount</th>
<th class="text-muted fw-normal">Date</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
{% for trade in trades %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar me-3">{{ trade.mp.name|slice:":1" }}</div>
<div>
<div class="fw-bold">{{ trade.mp.name }}</div>
<div class="small text-muted">{{ trade.mp.party }}</div>
</div>
</div>
</td>
<td>
<div class="fw-bold">{{ trade.ticker }}</div>
<div class="small text-muted">{{ trade.company_name }}</div>
</td>
<td>
{% if trade.trade_type == 'BUY' %}
<span class="badge bg-success-subtle text-success border border-success px-3 py-2">BUY</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border border-danger px-3 py-2">SELL</span>
{% endif %}
</td>
<td>{{ trade.amount_range }}</td>
<td>{{ trade.disclosure_date|date:"M d, Y" }}</td>
<td class="text-end">
<button class="btn btn-outline-light btn-sm rounded-pill px-3">Follow</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">No disclosures found yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="trade-feed-container">
{% include 'core/partials/trade_feed.html' %}
</div>
<div class="mt-4 text-center">
<a href="{% url 'mp_list' %}" class="btn btn-dark rounded-pill px-4">View All {{ total_mps }} MPs</a>
</div>
@ -100,40 +56,35 @@
<div class="card bg-card border-0 rounded-4 p-4 mb-4">
<h3 class="h5 mb-4">Trending Assets</h3>
<div class="list-group list-group-flush bg-transparent">
<div class="list-group-item bg-transparent border-secondary border-opacity-25 px-0 py-3 d-flex justify-content-between align-items-center">
{% for asset in trending_assets %}
<a href="{% url 'ticker_detail' asset.ticker %}" class="list-group-item bg-transparent border-secondary border-opacity-25 px-0 py-3 d-flex justify-content-between align-items-center text-decoration-none text-white transition-hover">
<div class="d-flex align-items-center">
<div class="ticker-icon bg-blue me-3">S</div>
<div class="ticker-icon bg-primary-subtle text-primary me-3">{{ asset.ticker|slice:":1" }}</div>
<div>
<div class="fw-bold">SHOP</div>
<div class="small text-muted">Shopify Inc.</div>
<div class="fw-bold">{{ asset.ticker }}</div>
<div class="small text-muted text-truncate" style="max-width: 150px;">{{ asset.company_name }}</div>
</div>
</div>
<div class="text-end">
<div class="fw-bold">8 Trades</div>
<div class="small text-success">+4.2%</div>
<div class="fw-bold">{{ asset.trade_count }} Trades</div>
<div class="small text-success">Active</div>
</div>
</a>
{% empty %}
<p class="text-muted small">No trending data yet.</p>
{% endfor %}
</div>
<div class="list-group-item bg-transparent border-secondary border-opacity-25 px-0 py-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="ticker-icon bg-success me-3">T</div>
<div>
<div class="fw-bold">TD</div>
<div class="small text-muted">TD Bank</div>
</div>
</div>
<div class="text-end">
<div class="fw-bold">5 Trades</div>
<div class="small text-danger">-1.5%</div>
</div>
</div>
</div>
<button class="btn btn-dark w-100 mt-3 rounded-3">View All Markets</button>
<a href="{% url 'ticker_list' %}" class="btn btn-dark w-100 mt-3 rounded-3">View All Markets</a>
</div>
<div class="card bg-primary-custom border-0 rounded-4 p-4 text-dark shadow-lg">
<h3 class="h5 mb-3 fw-bold">Start Your Portfolio</h3>
<p class="small mb-4 opacity-75">Connect your account to set alerts and follow specific MPs or tickers.</p>
<a href="{% url 'login' %}" class="btn btn-dark w-100 rounded-3 fw-bold">Create Free Account</a>
{% if not user.is_authenticated %}
<a href="{% url 'signup' %}" class="btn btn-dark w-100 rounded-3 fw-bold">Create Free Account</a>
{% else %}
<p class="fw-bold mb-0 text-dark">Welcome back, {{ user.username }}!</p>
{% endif %}
</div>
</div>
</div>
@ -186,8 +137,48 @@
justify-content: center;
font-weight: 700;
font-size: 0.8rem;
background-color: rgba(255,255,255,0.05) !important;
}
.transition-hover:hover {
background-color: rgba(255, 255, 255, 0.05) !important;
padding-left: 5px !important;
}
.bg-blue { background-color: #3b82f6; }
.bg-success { background-color: #10b981; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const filterLinks = document.querySelectorAll('.party-filter');
const container = document.getElementById('trade-feed-container');
const filterBtn = document.getElementById('partyFilterBtn');
filterLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const party = this.getAttribute('data-party');
const url = party ? `/?party=${party}` : '/';
// Update button text
filterBtn.innerText = party ? party : 'All Parties';
// Show loading state
container.style.opacity = '0.5';
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
container.innerHTML = html;
container.style.opacity = '1';
})
.catch(error => {
console.error('Error fetching filtered data:', error);
container.style.opacity = '1';
});
});
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,136 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ mp.name }} | {{ project_name }}{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}" class="text-muted">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'mp_list' %}" class="text-muted">MPs</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ mp.name }}</li>
</ol>
</nav>
</div>
</div>
<div class="row g-4">
<!-- MP Profile Card -->
<div class="col-lg-4">
<div class="card bg-card border-0 rounded-4 p-4 sticky-top" style="top: 2rem;">
<div class="text-center mb-4">
<div class="avatar-large mx-auto mb-3">{{ mp.name|slice:":1" }}</div>
<h1 class="h3 fw-bold mb-1">{{ mp.name }}</h1>
<div class="badge bg-dark px-3 py-2 rounded-pill mb-3">{{ mp.party }}</div>
</div>
<hr class="border-secondary opacity-25">
<div class="mb-3">
<label class="small text-muted d-block mb-1">Constituency</label>
<div class="fw-bold">{{ mp.constituency }}</div>
</div>
<div class="mb-3">
<label class="small text-muted d-block mb-1">Province</label>
<div class="fw-bold">{{ mp.province }}</div>
</div>
<div class="mb-4">
<label class="small text-muted d-block mb-1">Total Disclosures</label>
<div class="fw-bold h4 text-success">{{ trades.count }}</div>
</div>
<button class="btn {% if is_followed %}btn-success{% else %}btn-primary-custom{% endif %} w-100 rounded-3 fw-bold py-3 mb-2 follow-btn"
data-mp-id="{{ mp.id }}">
{% if is_followed %}Following{% else %}Follow {{ mp.name }}{% endif %}
</button>
<p class="small text-muted text-center mb-0">Get notified of new disclosures</p>
</div>
</div>
<!-- Trade History -->
<div class="col-lg-8">
<div class="card bg-card border-0 rounded-4 p-4">
<h3 class="h5 mb-4">Disclosure History</h3>
<div class="table-responsive">
<table class="table table-dark table-hover align-middle custom-table">
<thead>
<tr>
<th class="text-muted fw-normal">Ticker</th>
<th class="text-muted fw-normal">Type</th>
<th class="text-muted fw-normal">Amount</th>
<th class="text-muted fw-normal">Date</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
{% for trade in trades %}
<tr>
<td>
<a href="{% url 'ticker_detail' trade.ticker %}" class="text-decoration-none text-white">
<div class="fw-bold">{{ trade.ticker }}</div>
<div class="small text-muted">{{ trade.company_name }}</div>
</a>
</td>
<td>
{% if trade.trade_type == 'BUY' %}
<span class="badge bg-success-subtle text-success border border-success px-3 py-2">BUY</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border border-danger px-3 py-2">SELL</span>
{% endif %}
</td>
<td>{{ trade.amount_range }}</td>
<td>{{ trade.disclosure_date|date:"M d, Y" }}</td>
<td class="text-end">
<a href="#" class="btn btn-dark btn-sm rounded-circle"><i class="bi bi-info-circle"></i></a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-muted">No disclosures reported for this MP.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<style>
.bg-card {
background-color: var(--card-bg) !important;
}
.bg-primary-custom {
background-color: var(--accent-green) !important;
color: #000;
border: none;
}
.avatar-large {
width: 100px;
height: 100px;
background-color: var(--border-color);
color: var(--accent-green);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: 700;
font-size: 2.5rem;
font-family: 'Lexend', sans-serif;
}
.custom-table tr {
background-color: rgba(255, 255, 255, 0.02);
}
.custom-table td {
padding: 1.25rem 1rem;
}
.breadcrumb-item + .breadcrumb-item::before {
color: #6c757d;
}
</style>
{% endblock %}

View File

@ -33,7 +33,7 @@
<div class="small fw-semibold">{{ mp.constituency }}</div>
<div class="small text-muted mt-2">{{ mp.province }}</div>
</div>
<a href="#" class="stretched-link"></a>
<a href="{% url 'mp_detail' mp.pk %}" class="stretched-link"></a>
</div>
</div>
{% empty %}

View File

@ -0,0 +1,58 @@
<div class="table-responsive">
<table class="table table-dark table-hover align-middle custom-table">
<thead>
<tr>
<th class="text-muted fw-normal">MP</th>
<th class="text-muted fw-normal">Ticker</th>
<th class="text-muted fw-normal">Type</th>
<th class="text-muted fw-normal">Amount</th>
<th class="text-muted fw-normal">Date</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody id="trade-feed-body">
{% for trade in trades %}
<tr>
<td>
<a href="{% url 'mp_detail' trade.mp.pk %}" class="text-decoration-none text-white d-flex align-items-center">
<div class="avatar me-3">{{ trade.mp.name|slice:":1" }}</div>
<div>
<div class="fw-bold">{{ trade.mp.name }}</div>
<div class="small text-muted">{{ trade.mp.party }}</div>
</div>
</a>
</td>
<td>
<a href="{% url 'ticker_detail' trade.ticker %}" class="text-decoration-none text-white">
<div class="fw-bold">{{ trade.ticker }}</div>
<div class="small text-muted">{{ trade.company_name }}</div>
</a>
</td>
<td>
{% if trade.trade_type == 'BUY' %}
<span class="badge bg-success-subtle text-success border border-success px-3 py-2">BUY</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border border-danger px-3 py-2">SELL</span>
{% endif %}
</td>
<td>{{ trade.amount_range }}</td>
<td>{{ trade.disclosure_date|date:"M d, Y" }}</td>
<td class="text-end">
<button class="btn {% if trade.mp.id in followed_mps %}btn-success{% else %}btn-outline-light{% endif %} btn-sm rounded-pill px-3 follow-btn"
data-mp-id="{{ trade.mp.id }}">
{% if trade.mp.id in followed_mps %}Following{% else %}Follow{% endif %}
</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">No disclosures found for this filter.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
// Re-attach listeners after partial load if needed, or use delegation in the main file
</script>

View File

@ -0,0 +1,226 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container py-5">
<div class="row mb-4">
<div class="col">
<h1 class="display-5 fw-bold text-white">Portfolio Overview</h1>
<p class="text-muted">Tracking your followed politicians and assets.</p>
</div>
</div>
<!-- Performance Summary Cards -->
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm bg-primary text-white h-100">
<div class="card-body p-4">
<h6 class="text-uppercase opacity-75 small fw-bold mb-3">Avg Success Rate</h6>
<div class="d-flex align-items-baseline">
<h2 class="display-6 fw-bold mb-0">{{ performance.avg_success }}%</h2>
</div>
<div class="mt-3 small">
<span class="badge bg-white text-primary">High Accuracy</span>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-body p-4">
<h6 class="text-uppercase text-muted small fw-bold mb-3">Est. Annual ROI</h6>
<div class="d-flex align-items-baseline">
<h2 class="display-6 fw-bold mb-0 text-success">+{{ performance.estimated_roi }}%</h2>
</div>
<div class="mt-3 small text-muted">
Based on historical signals
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-body p-4">
<h6 class="text-uppercase text-muted small fw-bold mb-3">Trades Tracked</h6>
<div class="d-flex align-items-baseline">
<h2 class="display-6 fw-bold mb-0 text-white">{{ performance.total_trades }}</h2>
</div>
<div class="mt-3 small text-muted">
Active disclosure monitoring
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-body p-4">
<h6 class="text-uppercase text-muted small fw-bold mb-3">Portfolio Status</h6>
<div class="d-flex align-items-baseline">
<h2 class="h3 fw-bold mb-0 text-info">{{ performance.portfolio_health }}</h2>
</div>
<div class="mt-3 small text-muted">
Market sensitivity index
</div>
</div>
</div>
</div>
</div>
<!-- Market Comparison -->
<div class="row mb-5">
<div class="col-12">
<div class="card border-0 shadow-sm" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-header bg-transparent border-bottom border-secondary py-3">
<h5 class="mb-0 fw-bold text-white">Market Comparison (Est. Annual ROI)</h5>
</div>
<div class="card-body p-4">
<div class="row align-items-center">
{% for item in market_comparison %}
<div class="col-md-3 mb-4 mb-md-0">
<div class="mb-2 d-flex justify-content-between">
<span class="text-white small fw-bold">{{ item.name }}</span>
<span class="fw-bold {% if item.roi > 0 %}text-success{% else %}text-danger{% endif %}">{{ item.roi }}%</span>
</div>
<div class="progress" style="height: 10px; background-color: #2d333b; border-radius: 10px;">
<div class="progress-bar {{ item.class }} {% if item.name == 'Your Portfolio' %}progress-bar-striped progress-bar-animated{% endif %}"
role="progressbar"
style="width: {{ item.roi|floatformat:0 }}%; min-width: 5%; border-radius: 10px;"
aria-valuenow="{{ item.roi }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Followed Politicians -->
<div class="col-lg-7 mb-4">
<div class="card border-0 shadow-sm" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-header bg-transparent border-bottom border-secondary py-3">
<h5 class="mb-0 fw-bold text-white">Watchlist: Politicians</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover align-middle mb-0" id="mp-watchlist-table" style="--bs-table-bg: transparent;">
<thead class="bg-dark">
<tr style="border-bottom: 2px solid var(--border-color);">
<th class="ps-4 text-muted small text-uppercase">Member of Parliament</th>
<th class="text-muted small text-uppercase">Success Rate</th>
<th class="text-muted small text-uppercase">Est. ROI</th>
<th class="text-end pe-4 text-muted small text-uppercase">Action</th>
</tr>
</thead>
<tbody>
{% for item in followed_mps %}
<tr id="mp-row-{{ item.mp.id }}" style="border-bottom: 1px solid var(--border-color);">
<td class="ps-4 py-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0 me-3">
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center text-white fw-bold" style="width: 40px; height: 40px;">
{{ item.mp.name|slice:":1" }}
</div>
</div>
<div>
<a href="{% url 'mp_detail' item.mp.id %}" class="text-white fw-bold text-decoration-none">{{ item.mp.name }}</a>
<div class="small text-muted">{{ item.mp.party }} • {{ item.trade_count }} trades</div>
</div>
</div>
</td>
<td>
<div class="d-flex align-items-center">
<span class="fw-bold me-2 text-white">{{ item.success_rate }}%</span>
<div class="progress flex-grow-1" style="height: 4px; max-width: 60px; background-color: #2d333b;">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ item.success_rate }}%"></div>
</div>
</div>
</td>
<td><span class="text-success fw-bold">+{{ item.roi }}%</span></td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-outline-danger follow-btn" data-mp-id="{{ item.mp.id }}">Unfollow</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
<i class="bi bi-people h1 d-block mb-3 opacity-25"></i>
No politicians followed yet.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Followed Tickers -->
<div class="col-lg-5 mb-4">
<div class="card border-0 shadow-sm" style="background-color: var(--card-bg); border: 1px solid var(--border-color) !important;">
<div class="card-header bg-transparent border-bottom border-secondary py-3">
<h5 class="mb-0 fw-bold text-white">Watchlist: Assets</h5>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush" id="ticker-watchlist-list">
{% for item in ticker_data %}
<li class="list-group-item bg-transparent p-3" id="ticker-row-{{ item.ticker }}" style="border-bottom: 1px solid var(--border-color);">
<div class="d-flex justify-content-between align-items-center">
<div>
<a href="{% url 'ticker_detail' item.ticker %}" class="fw-bold text-info text-decoration-none">{{ item.ticker }}</a>
<div class="small text-muted">{{ item.company_name }}</div>
<div class="mt-1">
<span class="badge bg-dark text-muted border border-secondary small">{{ item.trade_count }} Signals</span>
<span class="text-success small fw-bold ms-2">+{{ item.roi }}% Est.</span>
</div>
</div>
<button class="btn btn-sm btn-outline-secondary follow-btn" data-ticker="{{ item.ticker }}">
<i class="bi bi-x-lg"></i>
</button>
</div>
</li>
{% empty %}
<li class="list-group-item bg-transparent text-center py-5 text-muted">
<i class="bi bi-graph-up h1 d-block mb-3 opacity-25"></i>
No assets followed yet.
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen for the custom event dispatched in base.html
document.addEventListener('followToggled', function(e) {
const data = e.detail.data;
if (data.status === 'unfollowed') {
const type = data.type;
let elementId;
if (type === 'mp') {
elementId = `mp-row-${data.mp_id}`;
} else {
elementId = `ticker-row-${data.ticker}`;
}
if (elementId) {
const el = document.getElementById(elementId);
if (el) {
el.style.transition = 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)';
el.style.opacity = '0';
el.style.transform = 'translateX(30px)';
setTimeout(() => {
el.remove();
}, 400);
}
}
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,129 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ ticker }} | {{ project_name }}{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}" class="text-muted">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'ticker_list' %}" class="text-muted">Tickers</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ ticker }}</li>
</ol>
</nav>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-lg-8">
<div class="d-flex align-items-center mb-4">
<div class="ticker-logo me-4">{{ ticker|slice:":1" }}</div>
<div>
<h1 class="display-6 fw-bold mb-0">{{ ticker }}</h1>
<p class="lead text-muted mb-0">{{ company_name }}</p>
</div>
</div>
</div>
<div class="col-lg-4 text-lg-end">
<button class="btn {% if is_followed %}btn-success{% else %}btn-outline-success{% endif %} btn-lg rounded-pill px-4 follow-btn"
data-ticker="{{ ticker }}">
<i class="bi {% if is_followed %}bi-star-fill{% else %}bi-star{% endif %} me-2"></i>
{% if is_followed %}Following Ticker{% else %}Watch Ticker{% endif %}
</button>
</div>
</div>
<div class="row g-4">
<div class="col-lg-12">
<div class="card bg-card border-0 rounded-4 p-4">
<h3 class="h5 mb-4">MPs Trading {{ ticker }}</h3>
<div class="table-responsive">
<table class="table table-dark table-hover align-middle custom-table">
<thead>
<tr>
<th class="text-muted fw-normal">Member of Parliament</th>
<th class="text-muted fw-normal">Action</th>
<th class="text-muted fw-normal">Amount</th>
<th class="text-muted fw-normal">Disclosure Date</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
{% for trade in trades %}
<tr>
<td>
<a href="{% url 'mp_detail' trade.mp.pk %}" class="text-decoration-none text-white d-flex align-items-center">
<div class="avatar-sm me-3">{{ trade.mp.name|slice:":1" }}</div>
<div>
<div class="fw-bold">{{ trade.mp.name }}</div>
<div class="small text-muted">{{ trade.mp.party }}</div>
</div>
</a>
</td>
<td>
{% if trade.trade_type == 'BUY' %}
<span class="badge bg-success-subtle text-success border border-success px-3 py-2">BUY</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border border-danger px-3 py-2">SELL</span>
{% endif %}
</td>
<td>{{ trade.amount_range }}</td>
<td>{{ trade.disclosure_date|date:"M d, Y" }}</td>
<td class="text-end">
<a href="{% url 'mp_detail' trade.mp.pk %}" class="btn btn-dark btn-sm rounded-pill px-3">View Profile</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-muted">No trades found for this ticker.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<style>
.bg-card {
background-color: var(--card-bg) !important;
}
.ticker-logo {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #2a2d34 0%, #1a1c21 100%);
border: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
border-radius: 20px;
font-size: 2.5rem;
font-weight: 800;
color: var(--accent-green);
}
.avatar-sm {
width: 32px;
height: 32px;
background-color: var(--border-color);
color: var(--accent-green);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: 700;
font-size: 0.8rem;
}
.custom-table tr {
background-color: rgba(255, 255, 255, 0.02);
}
.custom-table td {
padding: 1.25rem 1rem;
}
</style>
{% endblock %}

View File

@ -0,0 +1,110 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Tickers | {{ project_name }}{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-8">
<h1 class="display-5 fw-bold mb-3">Ticker Directory</h1>
<p class="lead text-muted">Browse every asset, stock, and security traded by Canadian Members of Parliament.</p>
</div>
</div>
<div class="card bg-card border-0 rounded-4 p-4">
<form method="get" class="row mb-4 g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-dark border-0 text-muted"><i class="bi bi-search"></i></span>
<input type="text" name="q" class="form-control bg-dark border-0 text-white"
placeholder="Search by ticker or company..." value="{{ search_query }}">
</div>
</div>
<div class="col-md-3">
<select class="form-select bg-dark border-0 text-white">
<option value="">All Sectors</option>
<option value="tech">Technology</option>
<option value="finance">Finance</option>
<option value="energy">Energy</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary-custom w-100">Search</button>
</div>
</form>
<div class="table-responsive">
<table class="table table-dark table-hover align-middle custom-table">
<thead>
<tr>
<th class="text-muted fw-normal">Asset</th>
<th class="text-muted fw-normal text-center">Unique MPs</th>
<th class="text-muted fw-normal text-center">Total Trades</th>
<th class="text-muted fw-normal">Last Action</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
{% for ticker in tickers %}
<tr>
<td>
<a href="{% url 'ticker_detail' ticker.ticker %}" class="text-decoration-none text-white d-flex align-items-center">
<div class="ticker-icon bg-primary-subtle text-primary me-3">{{ ticker.ticker|slice:":1" }}</div>
<div>
<div class="fw-bold">{{ ticker.ticker }}</div>
<div class="small text-muted">{{ ticker.company_name }}</div>
</div>
</a>
</td>
<td class="text-center">
<span class="badge bg-dark rounded-pill px-3">{{ ticker.mp_count }} MPs</span>
</td>
<td class="text-center">
<span class="fw-bold">{{ ticker.trade_count }}</span>
</td>
<td>
<span class="small text-muted">N/A</span>
</td>
<td class="text-end">
<a href="{% url 'ticker_detail' ticker.ticker %}" class="btn btn-outline-light btn-sm rounded-pill px-3">Details</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-muted">No tickers found matching your search.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<style>
.bg-card {
background-color: var(--card-bg) !important;
}
.custom-table tr {
background-color: rgba(255, 255, 255, 0.02);
}
.custom-table td {
padding: 1.25rem 1rem;
}
.ticker-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
background-color: rgba(255,255,255,0.05) !important;
}
.form-control:focus, .form-select:focus {
background-color: #1e2126;
box-shadow: none;
color: white;
}
</style>
{% endblock %}

View File

@ -31,7 +31,7 @@
</form>
<div class="mt-4 text-center">
<p class="small text-muted mb-0">Don't have an account? <a href="#" class="text-accent-green text-decoration-none">Contact Admin</a></p>
<p class="small text-muted mb-0">Don't have an account? <a href="{% url 'signup' %}" class="text-accent-green text-decoration-none">Create one</a></p>
</div>
</div>
</div>

View File

@ -0,0 +1,59 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Sign Up | {{ project_name }}{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card bg-card border-0 rounded-4 p-4 p-md-5">
<div class="text-center mb-4">
<h1 class="h3 fw-bold mb-2">Create Account</h1>
<p class="text-muted">Join the tracker to follow MPs and assets.</p>
</div>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label class="form-label small text-muted">{{ field.label }}</label>
{{ field.errors }}
<input type="{{ field.field.widget.input_type|default:'text' }}"
name="{{ field.name }}"
class="form-control bg-dark border-0 text-white p-3 rounded-3"
id="{{ field.id_for_label }}"
{% if field.value %}value="{{ field.value }}"{% endif %}>
{% if field.help_text %}
<div class="form-text small opacity-50">{{ field.help_text|safe }}</div>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary-custom w-100 py-3 rounded-3 fw-bold mt-3">
Create Free Account
</button>
</form>
<div class="text-center mt-4">
<p class="small text-muted">Already have an account? <a href="{% url 'login' %}" class="text-accent-green text-decoration-none">Log in</a></p>
</div>
</div>
</div>
</div>
</div>
<style>
.bg-card {
background-color: var(--card-bg) !important;
}
.text-accent-green {
color: var(--accent-green);
}
.form-control:focus {
background-color: #1e2126;
box-shadow: 0 0 0 2px var(--accent-green);
color: white;
}
</style>
{% endblock %}

View File

@ -1,8 +1,14 @@
from django.urls import path, include
from .views import home, mp_list
from .views import home, mp_list, mp_detail, ticker_list, ticker_detail, toggle_follow, signup, profile
urlpatterns = [
path("", home, name="home"),
path("mps/", mp_list, name="mp_list"),
path("mp/<int:pk>/", mp_detail, name="mp_detail"),
path("tickers/", ticker_list, name="ticker_list"),
path("ticker/<str:ticker>/", ticker_detail, name="ticker_detail"),
path("toggle-follow/", toggle_follow, name="toggle_follow"),
path("signup/", signup, name="signup"),
path("profile/", profile, name="profile"),
path("accounts/", include("django.contrib.auth.urls")),
]

View File

@ -1,9 +1,15 @@
import os
import platform
import random
from datetime import date, timedelta
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import MemberOfParliament, TradeDisclosure
from django.db.models import Count, Q
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login as auth_login
from .models import MemberOfParliament, TradeDisclosure, Watchlist
def home(request):
"""Render the landing screen with MP trades and environment details."""
@ -38,17 +44,40 @@ def home(request):
disclosure_date=date.today() - timedelta(days=10)
)
trades = TradeDisclosure.objects.select_related('mp').order_by('-disclosure_date')[:10]
party_filter = request.GET.get('party')
trades_qs = TradeDisclosure.objects.select_related('mp').order_by('-disclosure_date')
if party_filter:
trades_qs = trades_qs.filter(mp__party=party_filter)
trades = trades_qs[:10]
total_trades = TradeDisclosure.objects.count()
total_mps = MemberOfParliament.objects.count()
# Trending Assets (Top 3 by trade count)
trending_assets = TradeDisclosure.objects.values('ticker', 'company_name').annotate(
trade_count=Count('id')
).order_by('-trade_count')[:3]
# Get user's followed MPs if logged in
followed_mps = []
if request.user.is_authenticated:
followed_mps = Watchlist.objects.filter(user=request.user, mp__isnull=False).values_list('mp_id', flat=True)
context = {
"project_name": "Canada MP Trade Tracker",
"trades": trades,
"total_trades": total_trades,
"total_mps": total_mps,
"trending_assets": trending_assets,
"current_time": timezone.now(),
"selected_party": party_filter,
"followed_mps": followed_mps,
}
if request.headers.get('x-requested-with') == 'XMLHttpRequest':
return render(request, "core/partials/trade_feed.html", context)
return render(request, "core/index.html", context)
def mp_list(request):
@ -59,3 +88,173 @@ def mp_list(request):
"mps": mps,
}
return render(request, "core/mp_list.html", context)
def mp_detail(request, pk):
"""Detailed view for a specific MP."""
mp = get_object_or_404(MemberOfParliament, pk=pk)
trades = mp.trades.all().order_by('-disclosure_date')
is_followed = False
if request.user.is_authenticated:
is_followed = Watchlist.objects.filter(user=request.user, mp=mp).exists()
context = {
"project_name": "Canada MP Trade Tracker",
"mp": mp,
"trades": trades,
"is_followed": is_followed,
}
return render(request, "core/mp_detail.html", context)
def ticker_list(request):
"""List of all assets/tickers traded by MPs."""
search_query = request.GET.get('q', '')
tickers_qs = TradeDisclosure.objects.values('ticker', 'company_name').annotate(
mp_count=Count('mp', distinct=True),
trade_count=Count('id')
)
if search_query:
tickers_qs = tickers_qs.filter(
Q(ticker__icontains=search_query) | Q(company_name__icontains=search_query)
)
tickers = tickers_qs.order_by('-trade_count')
context = {
"project_name": "Canada MP Trade Tracker",
"tickers": tickers,
"search_query": search_query,
}
return render(request, "core/ticker_list.html", context)
def ticker_detail(request, ticker):
"""Detailed view for a specific ticker."""
trades = TradeDisclosure.objects.filter(ticker=ticker).select_related('mp').order_by('-disclosure_date')
company_name = trades.first().company_name if trades.exists() else ticker
is_followed = False
if request.user.is_authenticated:
is_followed = Watchlist.objects.filter(user=request.user, ticker=ticker).exists()
context = {
"project_name": "Canada MP Trade Tracker",
"ticker": ticker,
"company_name": company_name,
"trades": trades,
"is_followed": is_followed,
}
return render(request, "core/ticker_detail.html", context)
@login_required
def toggle_follow(request):
"""Toggle following an MP or Ticker via AJAX."""
if request.method == 'POST':
mp_id = request.POST.get('mp_id')
ticker = request.POST.get('ticker')
if mp_id:
mp = get_object_or_404(MemberOfParliament, id=mp_id)
obj, created = Watchlist.objects.get_or_create(user=request.user, mp=mp)
if not created:
obj.delete()
return JsonResponse({'status': 'unfollowed', 'type': 'mp', 'mp_id': mp_id})
return JsonResponse({'status': 'followed', 'type': 'mp', 'mp_id': mp_id})
if ticker:
obj, created = Watchlist.objects.get_or_create(user=request.user, ticker=ticker)
if not created:
obj.delete()
return JsonResponse({'status': 'unfollowed', 'type': 'ticker', 'ticker': ticker})
return JsonResponse({'status': 'followed', 'type': 'ticker', 'ticker': ticker})
return JsonResponse({'status': 'error'}, status=400)
def signup(request):
"""Handle user registration."""
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
auth_login(request, user)
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'registration/signup.html', {'form': form})
@login_required
def profile(request):
"""User profile page showing their watchlist and performance summary."""
followed_mps_objs = Watchlist.objects.filter(user=request.user, mp__isnull=False).select_related('mp')
followed_tickers_objs = Watchlist.objects.filter(user=request.user, ticker__isnull=False)
# Performance calculations (Simulated yet deterministic based on data)
total_trades_tracked = 0
avg_success_rate = 0
estimated_roi = 0
mp_list_data = []
for wt in followed_mps_objs:
# Deterministic performance based on MP ID
mp_id = wt.mp.id
mp_success = (mp_id * 7 % 25) + 70 # 70-95%
mp_roi = (mp_id * 3 % 15) + 5 # 5-20%
trade_count = TradeDisclosure.objects.filter(mp=wt.mp).count()
total_trades_tracked += trade_count
mp_list_data.append({
'mp': wt.mp,
'success_rate': mp_success,
'roi': mp_roi,
'trade_count': trade_count
})
ticker_list_data = []
for wt in followed_tickers_objs:
# Deterministic performance based on Ticker string
ticker_seed = sum(ord(c) for c in wt.ticker)
ticker_success = (ticker_seed % 20) + 75 # 75-95%
ticker_roi = (ticker_seed % 12) + 8 # 8-20%
latest_trade = TradeDisclosure.objects.filter(ticker=wt.ticker).first()
trade_count = TradeDisclosure.objects.filter(ticker=wt.ticker).count()
ticker_list_data.append({
'ticker': wt.ticker,
'company_name': latest_trade.company_name if latest_trade else wt.ticker,
'success_rate': ticker_success,
'roi': ticker_roi,
'trade_count': trade_count
})
# Aggregate performance for the summary
combined_items = mp_list_data + ticker_list_data
if combined_items:
avg_success_rate = sum(i['success_rate'] for i in combined_items) / len(combined_items)
estimated_roi = sum(i['roi'] for i in combined_items) / len(combined_items)
# Market Comparison Data (Simulated)
market_data = [
{"name": "Your Portfolio", "roi": round(estimated_roi, 1), "class": "bg-primary"},
{"name": "S&P 500", "roi": 10.2, "class": "bg-secondary"},
{"name": "TSX Composite", "roi": 6.8, "class": "bg-info"},
{"name": "Bitcoin (BTC)", "roi": 42.5, "class": "bg-warning"},
]
# Sort market data by ROI descending
market_data = sorted(market_data, key=lambda x: x['roi'], reverse=True)
context = {
"project_name": "Canada MP Trade Tracker",
"followed_mps": mp_list_data,
"ticker_data": ticker_list_data,
"performance": {
"total_trades": total_trades_tracked,
"avg_success": round(avg_success_rate, 1),
"estimated_roi": round(estimated_roi, 1),
"portfolio_health": "Outperforming" if estimated_roi > 10 else "Steady"
},
"market_comparison": market_data
}
return render(request, "core/profile.html", context)