Skip to content

Commit c65e56a

Browse files
Add stats page
1 parent 0870861 commit c65e56a

File tree

6 files changed

+464
-1
lines changed

6 files changed

+464
-1
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 4.2.27 on 2025-12-28 00:20
2+
3+
from django.db import migrations, models
4+
import django.utils.timezone
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('web', '0002_lookupdata_version1_lookupdata_version2'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='lookupdata',
16+
name='date_time',
17+
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
18+
preserve_default=False,
19+
),
20+
]

web/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ class SiteVisit(models.Model):
377377

378378
class LookupData(models.Model):
379379
id = models.BigAutoField(primary_key=True)
380-
date_time = models.DateTimeField
380+
date_time = models.DateTimeField(auto_now_add=True)
381381
language1 = models.CharField(max_length=50)
382382
version1 = models.CharField(max_length=20, default='')
383383
language2 = models.CharField(max_length=50)

web/templates/base.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
<li class="nav-item">
5656
<a class="nav-link" href="/about">About</a>
5757
</li>
58+
<li class="nav-item">
59+
<a class="nav-link" href="/statistics">Statistics</a>
60+
</li>
5861
<li class="nav-item">
5962
<a class="nav-link" href="https://docs.codethesaur.us">Docs</a>
6063
</li>

web/templates/statistics.html

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
{% extends 'base.html' %}
2+
3+
{% block content %}
4+
<div class="container mt-4">
5+
<div class="row">
6+
<div class="col-12">
7+
<h1>Site Statistics</h1>
8+
<p class="lead">Insights into how developers are using Code Thesaurus.</p>
9+
</div>
10+
</div>
11+
12+
<div class="row mt-4">
13+
<div class="col-md-4">
14+
<div class="card mb-4 shadow-sm h-100">
15+
<div class="card-header bg-primary text-white">
16+
<h5 class="mb-0"><i class="fas fa-users"></i> Overall Activity</h5>
17+
</div>
18+
<div class="card-body">
19+
<div class="row text-center mb-3">
20+
<div class="col-6">
21+
<h2 class="display-4">{{ total_visits }}</h2>
22+
<p class="text-muted">Total Visits</p>
23+
</div>
24+
<div class="col-6">
25+
<h2 class="display-4">{{ total_lookups }}</h2>
26+
<p class="text-muted">Total Lookups</p>
27+
</div>
28+
</div>
29+
<hr>
30+
<div class="row text-center">
31+
<div class="col-6 border-end">
32+
<h4 class="mb-0">{{ unique_comparisons_count }}</h4>
33+
<small class="text-muted">Unique Lang Comparisons</small>
34+
</div>
35+
<div class="col-6">
36+
<h4 class="mb-0">{{ unique_structures_count }}</h4>
37+
<small class="text-muted">Unique Category Lookups</small>
38+
</div>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
<div class="col-md-8">
44+
<div class="card mb-4 shadow-sm h-100">
45+
<div class="card-header bg-success text-white">
46+
<h5 class="mb-0"><i class="fas fa-chart-bar"></i> Language Popularity</h5>
47+
</div>
48+
<div class="card-body">
49+
<canvas id="languagesChart" height="100"></canvas>
50+
</div>
51+
</div>
52+
</div>
53+
</div>
54+
55+
<div class="row">
56+
<div class="col-12">
57+
<div class="card mb-4 shadow-sm">
58+
<div class="card-header bg-dark text-white">
59+
<h5 class="mb-0"><i class="fas fa-chart-line"></i> Most Popular Specific Concept Lookups</h5>
60+
</div>
61+
<div class="card-body">
62+
<p class="text-muted small">Comparing how often specific concepts are looked up for each language (e.g., "Javascript functions").</p>
63+
<canvas id="conceptLangsChart" height="80"></canvas>
64+
</div>
65+
</div>
66+
</div>
67+
</div>
68+
69+
<div class="row">
70+
<div class="col-12">
71+
<div class="card mb-4 shadow-sm">
72+
<div class="card-header bg-secondary text-white">
73+
<h5 class="mb-0"><i class="fas fa-history"></i> Recent Lookups</h5>
74+
</div>
75+
<div class="card-body">
76+
{% if recent_lookups %}
77+
<table class="table table-hover table-sm">
78+
<thead>
79+
<tr>
80+
<th>Time</th>
81+
<th>Language 1</th>
82+
<th>Language 2</th>
83+
<th>Concept</th>
84+
</tr>
85+
</thead>
86+
<tbody>
87+
{% for lookup in recent_lookups %}
88+
<tr>
89+
<td>{{ lookup.date_time|date:"Y-m-d H:i" }}</td>
90+
<td>{{ lookup.lang1 }}</td>
91+
<td>{% if lookup.lang2 %}{{ lookup.lang2 }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
92+
<td>{{ lookup.structure }}</td>
93+
</tr>
94+
{% endfor %}
95+
</tbody>
96+
</table>
97+
{% else %}
98+
<p class="text-center">No recent lookups recorded.</p>
99+
{% endif %}
100+
</div>
101+
</div>
102+
</div>
103+
</div>
104+
105+
<div class="row">
106+
<div class="col-12">
107+
<div class="card mb-4 shadow-sm">
108+
<div class="card-header bg-warning text-dark">
109+
<h5 class="mb-0"><i class="fas fa-exchange-alt"></i> Top Comparisons</h5>
110+
</div>
111+
<div class="card-body">
112+
{% if popular_comparisons %}
113+
<table class="table table-hover table-sm">
114+
<thead>
115+
<tr>
116+
<th>Language 1</th>
117+
<th>Language 2</th>
118+
<th class="text-end">Count</th>
119+
</tr>
120+
</thead>
121+
<tbody>
122+
{% for comp in popular_comparisons %}
123+
<tr>
124+
<td>{{ comp.lang1 }}</td>
125+
<td>{{ comp.lang2 }}</td>
126+
<td class="text-end">{{ comp.count }}</td>
127+
</tr>
128+
{% endfor %}
129+
</tbody>
130+
</table>
131+
{% else %}
132+
<p class="text-center">No comparisons made yet.</p>
133+
{% endif %}
134+
</div>
135+
</div>
136+
</div>
137+
</div>
138+
139+
<div class="row">
140+
<div class="col-md-6">
141+
<div class="card mb-4 shadow-sm">
142+
<div class="card-header bg-info text-white">
143+
<h5 class="mb-0"><i class="fas fa-chart-pie"></i> Category Popularity</h5>
144+
</div>
145+
<div class="card-body">
146+
<canvas id="structuresChart"></canvas>
147+
</div>
148+
</div>
149+
</div>
150+
<div class="col-md-6">
151+
<div class="card mb-4 shadow-sm">
152+
<div class="card-header bg-success text-white">
153+
<h5 class="mb-0"><i class="fas fa-language"></i> Most Popular Languages</h5>
154+
</div>
155+
<div class="card-body">
156+
{% if popular_languages %}
157+
<table class="table table-hover table-sm">
158+
<thead>
159+
<tr>
160+
<th>Language</th>
161+
<th class="text-end">Occurrences</th>
162+
</tr>
163+
</thead>
164+
<tbody>
165+
{% for lang in popular_languages %}
166+
<tr>
167+
<td>{{ lang.name }}</td>
168+
<td class="text-end">{{ lang.count }}</td>
169+
</tr>
170+
{% endfor %}
171+
</tbody>
172+
</table>
173+
{% else %}
174+
<p class="text-center">No data available yet.</p>
175+
{% endif %}
176+
</div>
177+
</div>
178+
</div>
179+
</div>
180+
181+
<div class="row">
182+
<div class="col-12">
183+
<div class="card mb-4 shadow-sm">
184+
<div class="card-header bg-info text-white">
185+
<h5 class="mb-0"><i class="fas fa-project-diagram"></i> Most Popular Concept Categories</h5>
186+
</div>
187+
<div class="card-body">
188+
{% if popular_structures %}
189+
<table class="table table-hover table-sm">
190+
<thead>
191+
<tr>
192+
<th>Category</th>
193+
<th class="text-end">Lookups</th>
194+
</tr>
195+
</thead>
196+
<tbody>
197+
{% for struct in popular_structures %}
198+
<tr>
199+
<td>{{ struct.name }}</td>
200+
<td class="text-end">{{ struct.count }}</td>
201+
</tr>
202+
{% endfor %}
203+
</tbody>
204+
</table>
205+
{% else %}
206+
<p class="text-center">No data available yet.</p>
207+
{% endif %}
208+
</div>
209+
</div>
210+
</div>
211+
</div>
212+
</div>
213+
214+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
215+
<script>
216+
document.addEventListener('DOMContentLoaded', function() {
217+
const langData = {{ popular_languages_json|safe }};
218+
const structData = {{ popular_structures_json|safe }};
219+
const conceptLangData = {{ popular_concept_langs_json|safe }};
220+
221+
if (langData.length > 0) {
222+
new Chart(document.getElementById('languagesChart'), {
223+
type: 'bar',
224+
data: {
225+
labels: langData.map(item => item.name),
226+
datasets: [{
227+
label: 'Lookups',
228+
data: langData.map(item => item.count),
229+
backgroundColor: 'rgba(40, 167, 69, 0.7)',
230+
borderColor: 'rgba(40, 167, 69, 1)',
231+
borderWidth: 1
232+
}]
233+
},
234+
options: {
235+
indexAxis: 'y',
236+
scales: {
237+
x: { beginAtZero: true }
238+
},
239+
plugins: {
240+
legend: { display: false }
241+
}
242+
}
243+
});
244+
}
245+
246+
if (structData.length > 0) {
247+
new Chart(document.getElementById('structuresChart'), {
248+
type: 'doughnut',
249+
data: {
250+
labels: structData.map(item => item.name),
251+
datasets: [{
252+
data: structData.map(item => item.count),
253+
backgroundColor: [
254+
'#17a2b8', '#28a745', '#ffc107', '#dc3545', '#007bff',
255+
'#6610f2', '#e83e8c', '#fd7e14', '#20c997', '#6c757d'
256+
]
257+
}]
258+
},
259+
options: {
260+
plugins: {
261+
legend: { position: 'right' }
262+
}
263+
}
264+
});
265+
}
266+
267+
if (conceptLangData.length > 0) {
268+
new Chart(document.getElementById('conceptLangsChart'), {
269+
type: 'bar',
270+
data: {
271+
labels: conceptLangData.map(item => item.label),
272+
datasets: [{
273+
label: 'Lookups',
274+
data: conceptLangData.map(item => item.count),
275+
backgroundColor: 'rgba(52, 58, 64, 0.7)',
276+
borderColor: 'rgba(52, 58, 64, 1)',
277+
borderWidth: 1
278+
}]
279+
},
280+
options: {
281+
scales: {
282+
y: { beginAtZero: true }
283+
},
284+
plugins: {
285+
legend: { display: false }
286+
}
287+
}
288+
});
289+
}
290+
});
291+
</script>
292+
{% endblock %}

web/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
# /about/
1717
path('about/', views.about, name='about'),
1818

19+
# /statistics/
20+
path('statistics/', views.statistics, name='statistics'),
21+
1922
# /compare/lang1/lang2
2023
#path('<str:lang1>/<str:lang2>/', views.detail, name='detail')
2124
# /compare/

0 commit comments

Comments
 (0)