Skip to content

Commit 1ea24f3

Browse files
committed
feat: run matches from rankings list
1 parent 1ddf2a6 commit 1ea24f3

7 files changed

Lines changed: 129 additions & 10 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.10 on 2026-02-02 21:35
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('authentication', '0010_user_rating_ratinghistory'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='user',
15+
name='rating',
16+
field=models.DecimalField(decimal_places=2, default=1000.0, max_digits=6),
17+
),
18+
]

othello/apps/auth/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class User(AbstractUser):
66
is_teacher = models.BooleanField(default=False, null=False)
77
is_student = models.BooleanField(default=True, null=False)
88
is_imported = models.BooleanField(default=False, null=False)
9-
rating = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
9+
rating = models.DecimalField(max_digits=6, decimal_places=2, default=1000.00)
1010

1111
@property
1212
def has_management_permission(self) -> bool:

othello/apps/auth/views.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import json
2+
import logging
23

34
from django.contrib.auth import get_user_model
45
from django.contrib.auth.decorators import login_required
56
from django.core.paginator import Paginator
67
from django.http import HttpRequest, HttpResponse
78
from django.shortcuts import get_object_or_404, render
89

9-
from .models import RatingHistory
10+
from ..games.forms import MatchForm
11+
from ..games.models import Submission, RatingHistory
1012

1113

14+
logger = logging.getLogger("othello")
15+
1216
def index(request: HttpRequest) -> HttpResponse:
1317
return render(request, "auth/index.html")
1418

@@ -65,10 +69,14 @@ def rankings(request: HttpRequest) -> HttpResponse:
6569
paginator = Paginator(users, 25) # Show 25 users per page
6670
page_number = request.GET.get("page")
6771
page_obj = paginator.get_page(page_number)
72+
form = MatchForm(request.user)
73+
users_with_submissions = set(Submission.objects.values_list('user__username', flat=True).distinct()) - {request.user.username, "Yourself"}
6874
return render(
6975
request,
7076
"auth/rankings.html",
7177
{
7278
"page_obj": page_obj,
79+
"form": form,
80+
"users_with_submissions": users_with_submissions,
7381
},
7482
)

othello/apps/games/forms.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,35 @@ class Meta:
7676

7777

7878
class MatchForm(forms.Form):
79-
opponent = forms.ModelChoiceField(label="Opponent:", queryset=Submission.objects.none())
80-
num_games = forms.IntegerField(label="Number of Games:", initial=3, min_value=1, max_value=10)
79+
opponent = forms.ModelChoiceField(
80+
label="Opponent:",
81+
queryset=Submission.objects.none(),
82+
)
83+
num_games = forms.IntegerField(
84+
label="Number of Games:",
85+
initial=3,
86+
min_value=1,
87+
max_value=10,
88+
)
8189

82-
def __init__(self, user, *args: Any, **kwargs: Any) -> None:
90+
def __init__(self, user, *args, initial_opponent_user=None, **kwargs):
8391
super().__init__(*args, **kwargs)
92+
8493
opponents = (
85-
Submission.objects.latest().exclude(user=user).exclude(user__username="Yourself")
94+
Submission.objects.latest()
95+
.exclude(user=user)
96+
.exclude(user__username="Yourself")
8697
)
8798
self.fields["opponent"].queryset = opponents
8899
self.fields["opponent"].label_from_instance = Submission.get_game_name
100+
101+
if initial_opponent_user:
102+
try:
103+
opponent_submission = (
104+
Submission.objects
105+
.filter(user=initial_opponent_user)
106+
.latest(onesub=True)
107+
)
108+
self.fields["opponent"].initial = opponent_submission
109+
except Submission.DoesNotExist:
110+
pass

othello/apps/games/models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ def calculate_results(self) -> None:
196196
]
197197
)
198198

199-
# --- Elo update ---
200199
user1 = self.player1.user
201200
user2 = self.player2.user
202201

@@ -208,8 +207,7 @@ def calculate_results(self) -> None:
208207
rating2=rating2,
209208
player1_wins=player1_wins,
210209
player2_wins=player2_wins,
211-
ties=ties,
212-
k=32, # tune this
210+
ties=ties
213211
)
214212

215213
user1.rating = rating1 + change1

othello/apps/games/views.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33

44
from django.contrib import messages
5+
from django.contrib.auth import get_user_model
56
from django.contrib.auth.decorators import login_required
67
from django.core.paginator import Paginator
78
from django.db import models
@@ -155,7 +156,21 @@ def request_match(request: HttpRequest) -> HttpResponse:
155156
for error in errors:
156157
messages.error(request, error["message"], extra_tags="danger")
157158
else:
158-
form = MatchForm(request.user)
159+
initial_opponent_user = None
160+
opponent_username = request.GET.get("opponent")
161+
if opponent_username:
162+
try:
163+
initial_opponent_user = get_user_model().objects.get(
164+
username=opponent_username
165+
)
166+
except get_user_model().DoesNotExist:
167+
pass
168+
169+
form = MatchForm(
170+
request.user,
171+
initial_opponent_user=initial_opponent_user,
172+
)
173+
159174
return render(request, "games/request_match.html", {"form": form})
160175

161176

othello/templates/auth/rankings.html

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ <h1>User Rankings</h1>
1212
<th>Rank</th>
1313
<th>Username</th>
1414
<th>Rating</th>
15+
<th>Action</th>
1516
</tr>
1617
</thead>
1718
<tbody>
@@ -20,6 +21,7 @@ <h1>User Rankings</h1>
2021
<td>{{ page_obj.start_index|add:forloop.counter|add:-1 }}</td>
2122
<td><a href="{% url 'auth:profile' user.username %}">{{ user.username }}</a></td>
2223
<td>{{ user.rating }}</td>
24+
<td>{% if user.username in users_with_submissions %}<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#requestMatchModal" data-username="{{ user.username }}">Request Match</button>{% endif %}</td>
2325
</tr>
2426
{% endfor %}
2527
</tbody>
@@ -41,4 +43,60 @@ <h1>User Rankings</h1>
4143
</ul>
4244
</nav>
4345
</div>
46+
47+
<div class="modal fade" id="requestMatchModal" tabindex="-1" role="dialog" aria-labelledby="requestMatchModalLabel" aria-hidden="true">
48+
<div class="modal-dialog" role="document">
49+
<div class="modal-content">
50+
<div class="modal-header">
51+
<h5 class="modal-title" id="requestMatchModalLabel">Request a Match</h5>
52+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
53+
<span aria-hidden="true">&times;</span>
54+
</button>
55+
</div>
56+
<form id="requestMatchForm" method="post" action="{% url 'games:request_match' %}">
57+
{% csrf_token %}
58+
<div class="modal-body">
59+
{{ form.as_p }}
60+
</div>
61+
<div class="modal-footer">
62+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
63+
<button type="submit" class="btn btn-primary">Request Match</button>
64+
</div>
65+
</form>
66+
</div>
67+
</div>
68+
</div>
69+
70+
<script>
71+
$('#requestMatchModal').on('show.bs.modal', function (event) {
72+
var button = $(event.relatedTarget);
73+
var username = button.data('username');
74+
var form = $('#requestMatchForm');
75+
var action = form.attr('action');
76+
if (action.indexOf('?') > -1) {
77+
action = action.split('?')[0];
78+
}
79+
form.attr('action', action + '?opponent=' + encodeURIComponent(username));
80+
81+
var select = form.find('select[name="opponent"]');
82+
select.find('option').each(function() {
83+
if ($(this).text().indexOf('(' + username + ')') !== -1) {
84+
select.val($(this).val());
85+
return false;
86+
}
87+
});
88+
89+
select.prop('disabled', true);
90+
});
91+
92+
$('#requestMatchModal').on('hidden.bs.modal', function () {
93+
var form = $('#requestMatchForm');
94+
var select = form.find('select[name="opponent"]');
95+
select.prop('disabled', false);
96+
});
97+
98+
$('#requestMatchForm').on('submit', function() {
99+
$(this).find('select[name="opponent"]').prop('disabled', false);
100+
});
101+
</script>
44102
{% endblock %}

0 commit comments

Comments
 (0)