Mock 5
This commit is contained in:
parent
e083afc1d5
commit
16066e4ba4
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
@ -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 %}
|
||||
136
core/templates/core/mp_detail.html
Normal file
136
core/templates/core/mp_detail.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
|
||||
58
core/templates/core/partials/trade_feed.html
Normal file
58
core/templates/core/partials/trade_feed.html
Normal 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>
|
||||
226
core/templates/core/profile.html
Normal file
226
core/templates/core/profile.html
Normal 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 %}
|
||||
129
core/templates/core/ticker_detail.html
Normal file
129
core/templates/core/ticker_detail.html
Normal 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 %}
|
||||
110
core/templates/core/ticker_list.html
Normal file
110
core/templates/core/ticker_list.html
Normal 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 %}
|
||||
@ -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>
|
||||
|
||||
59
core/templates/registration/signup.html
Normal file
59
core/templates/registration/signup.html
Normal 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 %}
|
||||
@ -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")),
|
||||
]
|
||||
|
||||
205
core/views.py
205
core/views.py
@ -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)
|
||||
Loading…
x
Reference in New Issue
Block a user