diff --git a/boatmovers/migrations/0008_auto_20220624_1135.py b/boatmovers/migrations/0008_auto_20220624_1135.py new file mode 100644 index 00000000..d5492e47 --- /dev/null +++ b/boatmovers/migrations/0008_auto_20220624_1135.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2022-06-24 11:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('boatmovers', '0007_auto_20220624_0820'), + ] + + operations = [ + migrations.AddField( + model_name='race', + name='processed', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='race', + name='crew_size', + field=models.IntegerField(default=1, verbose_name='Nr of rowers per crew (1, 2, 4, 8)'), + ), + ] diff --git a/boatmovers/migrations/0009_alter_race_crew_size.py b/boatmovers/migrations/0009_alter_race_crew_size.py new file mode 100644 index 00000000..4207ae30 --- /dev/null +++ b/boatmovers/migrations/0009_alter_race_crew_size.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-06-24 12:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('boatmovers', '0008_auto_20220624_1135'), + ] + + operations = [ + migrations.AlterField( + model_name='race', + name='crew_size', + field=models.IntegerField(choices=[(1, 1), (2, 2), (4, 4), (8, 8)], default=1, verbose_name='Nr of rowers per crew (1, 2, 4, 8)'), + ), + ] diff --git a/boatmovers/migrations/0010_remove_race_resultlist.py b/boatmovers/migrations/0010_remove_race_resultlist.py new file mode 100644 index 00000000..e6d9403e --- /dev/null +++ b/boatmovers/migrations/0010_remove_race_resultlist.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2022-06-24 12:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('boatmovers', '0009_alter_race_crew_size'), + ] + + operations = [ + migrations.RemoveField( + model_name='race', + name='resultlist', + ), + ] diff --git a/boatmovers/migrations/0011_alter_race_processed.py b/boatmovers/migrations/0011_alter_race_processed.py new file mode 100644 index 00000000..f8b81cb7 --- /dev/null +++ b/boatmovers/migrations/0011_alter_race_processed.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-06-24 12:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('boatmovers', '0010_remove_race_resultlist'), + ] + + operations = [ + migrations.AlterField( + model_name='race', + name='processed', + field=models.BooleanField(default=False), + ), + ] diff --git a/boatmovers/models.py b/boatmovers/models.py index 8785c275..b2664c4b 100644 --- a/boatmovers/models.py +++ b/boatmovers/models.py @@ -52,6 +52,9 @@ class Crew(models.Model): def save(self, *args, **kwargs): super(Crew, self).save(*args, **kwargs) + def size(self): + return self.athletes.all().count() + class crewForm(forms.ModelForm): class Meta: model = Crew @@ -59,12 +62,13 @@ class crewForm(forms.ModelForm): class Race(models.Model): name = models.CharField(max_length=200) - resulturl = models.URLField(null=True) - date = models.DateField(default=current_day) - resultlist = models.ManyToManyField(Crew,through='Result') - crew_size = models.IntegerField(default=1,verbose_name='Nr of rowers per crew (1, 2, 4, 8)') + resulturl = models.URLField(null=True, verbose_name='URL Link to results') + date = models.DateField(default=current_day, verbose_name='Race Date') + #resultlist = models.ManyToManyField(Result,through='Result') + crew_size = models.IntegerField(default=1,verbose_name='Nr of rowers per crew (1, 2, 4, 8)', + choices=((1,1),(2,2),(4,4),(8,8))) verified = models.BooleanField(default=False) - processed = models.BooleanField(default=True) + processed = models.BooleanField(default=False) class Meta: unique_together = ('date','name') @@ -93,10 +97,87 @@ class Race(models.Model): super(Race, self).save(*args, **kwargs) + def validate(self): + if len(self.results.all()) < 2: + self.verified = False + self.save() + return False + + l = self.results.all()[0].crew.size() + for result in self.results.all(): + if result.crew.size() != l: + self.verified = False + self.save() + return False + + if l not in [1,2,4,8]: + self.verified = False + self.save() + return False + + results = self.results.all() + crews = [] + athletes = [] + for result in results: + crews.append(result.crew.id) + for athlete in result.crew.athletes.all(): + athletes.append(athlete.id) + + if len(crews) != len(set(crews)): + self.verified = False + self.save() + return False + + if len(athletes) != len(set(athletes)): + self.verified = False + self.save() + return False + + self.verified = True + self.save() + + def process(self): + if not self.verified: + if not self.validate(): + return False + + if self.processed: + return True + + # validate the race + results = self.results.all().order_by('order') + crews = [] + ranks = [] + + for result in results: + crew = result.crew + crewdict = {} + for athlete in crew.athletes.all(): + crewdict[athlete.id] = trueskill.Rating( + athlete.trueskill_mu, athlete.trueskill_sigma) + crews.append(crewdict) + ranks.append(result.order) + + rated_crews = trueskill.rate(crews, ranks) + + for crew in rated_crews: + + for id, rating in crew.items(): + athlete = Athlete.objects.get(id=id) + athlete.trueskill_mu = rating.mu + athlete.trueskill_sigma = rating.sigma + athlete.save() + + self.processed = True + self.save() + + return True + + class raceForm(forms.ModelForm): class Meta: model = Race - fields = ['name','date','resulturl','crew_size','resultlist'] + fields = ['name','date','resulturl','crew_size'] class Result(models.Model): @@ -107,7 +188,14 @@ class Result(models.Model): order = models.PositiveIntegerField() class Meta: - unique_together = ('crew','race','order') + unique_together = ('crew','order') + + def __str__(self): + return u'{r}: {o} - {c}'.format( + r=self.race, + o=self.order, + c=self.crew, + ) def save(self, *args, **kwargs): allresults = self.race.results.all() diff --git a/boatmovers/results.py b/boatmovers/results.py index ebfdc0df..168a51a6 100644 --- a/boatmovers/results.py +++ b/boatmovers/results.py @@ -38,30 +38,30 @@ class Result: def __init__(self, crews, name, validated=False, processed=False): self.crews = crews self.name = name - self.validated = validated + self.verified = validated self.processed = processed def validate(self): # crews need to be more than 2 if len(self.crews) < 2: - self.validated = False + self.verified = False return False # crews need to be all same length l = self.crews[0].size() for crew in self.crews: if crew.size() != l: - self.validated = False + self.verified = False return False # crew length need to be 1, 2, 4 or 8 if l not in [1,2,4,8]: - self.validated = False + self.verified = False return False # cannot have same crew multiple times in same race if len(self.crews) != len(set(self.crews)): - self.validated = False + self.verified = False return False # cannot have same athletes in different crews in same race @@ -71,14 +71,14 @@ class Result: allathletes.append(athlete) if len(allathletes) != len(set(allathletes)): - self.validated = False + self.verified = False return False - self.validated = True - return self.validated + self.verified = True + return self.verified def process(self): - if not self.validated: + if not self.verified: if not self.validate(): return False diff --git a/boatmovers/templates/boatmovers.html b/boatmovers/templates/boatmovers.html new file mode 100644 index 00000000..8337956e --- /dev/null +++ b/boatmovers/templates/boatmovers.html @@ -0,0 +1,69 @@ +

+ + + + + + + + + + {% for athlete in athletes %} + + + + + + + + + + {% endfor %} +
RankScoreNameClubGenderYear of Birth
{{ forloop.counter }}{{ athlete.trueskill_exposed|floatformat:2 }}{{ athlete.first_name }}{{ athlete.last_name }}{{ athlete.club }}{{ athlete.gender }}{{ athlete.birth_year }}
+

+

+ This ranking was based on results from following races: +

+

+ + {% for race in races %} + + + + + {% endfor %} +
{{ race.date }}{{ race.name }} + View Race +
+

+{% if user.is_authenticated and user.is_staff %} +

+ Unprocessed races +

+

+ + {% for race in new_races %} + + + + + {% endfor %} +
{{ race.date }}{{ race.name }} + Manage Race +
+

+

+ Add Athlete +

+

+ Add Crew +

+{% endif %} +

+ Add Race +

+{% if user.is_authenticated and user.is_staff %} +

+ Add Result +

+{% endif %} diff --git a/boatmovers/templates/boatmovers/race_form.html b/boatmovers/templates/boatmovers/race_form.html new file mode 100644 index 00000000..a9b32c5f --- /dev/null +++ b/boatmovers/templates/boatmovers/race_form.html @@ -0,0 +1,4 @@ +
{% csrf_token %} + {{ form.as_p }} + +
diff --git a/boatmovers/templates/boatmovers/result_form.html b/boatmovers/templates/boatmovers/result_form.html new file mode 100644 index 00000000..a9b32c5f --- /dev/null +++ b/boatmovers/templates/boatmovers/result_form.html @@ -0,0 +1,4 @@ +
{% csrf_token %} + {{ form.as_p }} + +
diff --git a/boatmovers/templates/race.html b/boatmovers/templates/race.html new file mode 100644 index 00000000..c26bcbff --- /dev/null +++ b/boatmovers/templates/race.html @@ -0,0 +1,45 @@ +

+ {{ race.name }} +

+

+ {{ race.date }} +

+

+ + + + + + {% for result in results %} + + + + + {% endfor %} +
OrderCrew
{{ result.order }}{{ result.crew.name }}
+

+{% if user.is_authenticated and user.is_staff %} +{% if race.verified %} +

+ Race has been verified +

+{% if race.processed %} +

+ Race has been processed +

+{% else %} +

+ Race is not processed. Process Race +

+{% endif %} +{% else %} +

+ Race is not verified. Verify Race +

+{% endif %} +{% if not race.verified and not race.processed %} +

+ Add Result +

+{% endif %} +{% endif %} diff --git a/boatmovers/urls.py b/boatmovers/urls.py index 71b8da8e..0a849670 100644 --- a/boatmovers/urls.py +++ b/boatmovers/urls.py @@ -7,5 +7,10 @@ import boatmovers.views as views urlpatterns = [ url(r'athlete/add/$',views.AthleteCreateView.as_view(),name='athlete_add'), url(r'crew/add/$',views.CrewCreateView.as_view(),name='crew_add'), + url(r'race/add/$',views.RaceCreateView.as_view(),name='race_add'), + url(r'result/add/$',views.ResultCreateView.as_view(),name='result_add'), + url(r'race/(?P\d+)/$',views.race_view,name='race_view'), + url(r'race/(?P\d+)/verify/$',views.race_verify,name='race_verify'), + url(r'race/(?P\d+)/process/$',views.race_process,name='race_process'), url(r'^$',views.boatmovers_view,name='boatmovers') ] diff --git a/boatmovers/views.py b/boatmovers/views.py index 04ab5de1..65ed0bea 100644 --- a/boatmovers/views.py +++ b/boatmovers/views.py @@ -1,9 +1,11 @@ from django.shortcuts import render -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.urls import reverse # Create your views here. from django.views.generic.edit import CreateView -from boatmovers.models import Athlete, Crew +from boatmovers.models import Athlete, Crew, Race, Result class AthleteCreateView(CreateView): model = Athlete @@ -25,5 +27,59 @@ class CrewCreateView(CreateView): success_url = '/boatmovers/' +class RaceCreateView(CreateView): + model = Race + fields = [ + 'name', + 'resulturl', + 'date', + 'crew_size', + #'resultlist', + ] + + success_url = '/boatmovers/' + +class ResultCreateView(CreateView): + model = Result + fields = [ + 'crew', + 'race', + 'order' + ] + + success_url = '/boatmovers/' + def boatmovers_view(request): - return HttpResponse("1") + athletes = Athlete.objects.all().order_by('-trueskill_exposed','-birth_year','last_name','first_name') + + races = Race.objects.filter(verified=True,processed=True).order_by('-date') + new_races = Race.objects.filter(processed=False).order_by('date') + + return render(request, + 'boatmovers.html', + {'athletes':athletes, + 'races': races, + 'new_races': new_races} + ) + +def race_view(request,id=0): + race = get_object_or_404(Race, pk=id) + results = race.results.all().order_by('order') + + return render(request, + 'race.html', + {'race':race, + 'results':results} + ) + +def race_verify(request, id=0): + race = get_object_or_404(Race, pk=id) + outcome = race.validate() + + return HttpResponseRedirect(reverse('race_view',kwargs={'id':race.id})) + +def race_process(request, id=0): + race = get_object_or_404(Race, pk=id) + outcome = race.process() + + return HttpResponseRedirect(reverse('race_view',kwargs={'id':race.id}))