Merge branch 'release/v18.5.16'
This commit is contained in:
0
boatmovers/__init__.py
Normal file
0
boatmovers/__init__.py
Normal file
36
boatmovers/admin.py
Normal file
36
boatmovers/admin.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from.models import Athlete, Crew, Race, Result
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
class AthleteInline(admin.StackedInline):
|
||||||
|
model = Athlete
|
||||||
|
|
||||||
|
class AthleteAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('first_name', 'last_name', 'birth_year')
|
||||||
|
|
||||||
|
class CrewInline(admin.StackedInline):
|
||||||
|
model = Athlete
|
||||||
|
|
||||||
|
class CrewAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name',)
|
||||||
|
|
||||||
|
class RaceInline(admin.StackedInline):
|
||||||
|
model = Race
|
||||||
|
|
||||||
|
class RaceAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name',)
|
||||||
|
|
||||||
|
class ResultInline(admin.StackedInline):
|
||||||
|
model = Result
|
||||||
|
|
||||||
|
class ResultAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('race','crew','order')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Athlete, AthleteAdmin)
|
||||||
|
admin.site.register(Crew, CrewAdmin)
|
||||||
|
admin.site.register(Race, RaceAdmin)
|
||||||
|
admin.site.register(Result, ResultAdmin)
|
||||||
6
boatmovers/apps.py
Normal file
6
boatmovers/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BoatmoversConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'boatmovers'
|
||||||
5
boatmovers/forms.py
Normal file
5
boatmovers/forms.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
class CsvForm(forms.Form):
|
||||||
|
file = forms.FileField(label='CSV File')
|
||||||
|
# comment = forms.CharField(required=False)
|
||||||
58
boatmovers/migrations/0001_initial.py
Normal file
58
boatmovers/migrations/0001_initial.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-22 17:41
|
||||||
|
|
||||||
|
import boatmovers.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Athlete',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('first_name', models.CharField(max_length=200)),
|
||||||
|
('last_name', models.CharField(max_length=200)),
|
||||||
|
('club', models.CharField(max_length=200)),
|
||||||
|
('trueskill_mu', models.FloatField(default=25.0)),
|
||||||
|
('trueskill_sigma', models.FloatField(default=8.333)),
|
||||||
|
('birth_year', models.IntegerField(default=1972)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Crew',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('athletes', models.ManyToManyField(to='boatmovers.Athlete')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Race',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date', models.DateField(default=boatmovers.models.current_day)),
|
||||||
|
('crew_size', models.IntegerField(default=1)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Result',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('order', models.PositiveIntegerField()),
|
||||||
|
('crew', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boatmovers.crew')),
|
||||||
|
('race', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boatmovers.race')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='race',
|
||||||
|
name='resultlist',
|
||||||
|
field=models.ManyToManyField(through='boatmovers.Result', to='boatmovers.Crew'),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
boatmovers/migrations/0002_alter_athlete_unique_together.py
Normal file
17
boatmovers/migrations/0002_alter_athlete_unique_together.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-22 17:49
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='athlete',
|
||||||
|
unique_together={('first_name', 'last_name', 'birth_year')},
|
||||||
|
),
|
||||||
|
]
|
||||||
28
boatmovers/migrations/0003_auto_20220622_1753.py
Normal file
28
boatmovers/migrations/0003_auto_20220622_1753.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-22 17:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0002_alter_athlete_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='race',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default='Race1', max_length=200),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='race',
|
||||||
|
name='resulturl',
|
||||||
|
field=models.URLField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='race',
|
||||||
|
unique_together={('date', 'name')},
|
||||||
|
),
|
||||||
|
]
|
||||||
22
boatmovers/migrations/0004_auto_20220622_1835.py
Normal file
22
boatmovers/migrations/0004_auto_20220622_1835.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-22 18:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0003_auto_20220622_1753'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='race',
|
||||||
|
name='verified',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='result',
|
||||||
|
unique_together={('crew', 'race', 'order')},
|
||||||
|
),
|
||||||
|
]
|
||||||
19
boatmovers/migrations/0005_athlete_gender.py
Normal file
19
boatmovers/migrations/0005_athlete_gender.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-22 18:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0004_auto_20220622_1835'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='athlete',
|
||||||
|
name='gender',
|
||||||
|
field=models.CharField(choices=[('m', 'M'), ('f', 'F')], default='m', max_length=200),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
23
boatmovers/migrations/0006_auto_20220624_0811.py
Normal file
23
boatmovers/migrations/0006_auto_20220624_0811.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-24 08:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0005_athlete_gender'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='athlete',
|
||||||
|
name='trueskill_exposed',
|
||||||
|
field=models.FloatField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='athlete',
|
||||||
|
name='trueskill_sigma',
|
||||||
|
field=models.FloatField(default=8.333333333333334),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
boatmovers/migrations/0007_auto_20220624_0820.py
Normal file
24
boatmovers/migrations/0007_auto_20220624_0820.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-24 08:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0006_auto_20220624_0811'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='result',
|
||||||
|
name='crew',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='boatmovers.crew'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='result',
|
||||||
|
name='race',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='boatmovers.race'),
|
||||||
|
),
|
||||||
|
]
|
||||||
23
boatmovers/migrations/0008_auto_20220624_1135.py
Normal file
23
boatmovers/migrations/0008_auto_20220624_1135.py
Normal file
@@ -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)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
boatmovers/migrations/0009_alter_race_crew_size.py
Normal file
18
boatmovers/migrations/0009_alter_race_crew_size.py
Normal file
@@ -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)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
boatmovers/migrations/0010_remove_race_resultlist.py
Normal file
17
boatmovers/migrations/0010_remove_race_resultlist.py
Normal file
@@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
boatmovers/migrations/0011_alter_race_processed.py
Normal file
18
boatmovers/migrations/0011_alter_race_processed.py
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
33
boatmovers/migrations/0012_auto_20220625_1328.py
Normal file
33
boatmovers/migrations/0012_auto_20220625_1328.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-25 13:28
|
||||||
|
|
||||||
|
import boatmovers.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0011_alter_race_processed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='crew',
|
||||||
|
name='athletes',
|
||||||
|
field=models.ManyToManyField(related_name='crews', to='boatmovers.Athlete'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='race',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(default=boatmovers.models.current_day, verbose_name='Race Date'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='race',
|
||||||
|
name='resulturl',
|
||||||
|
field=models.URLField(null=True, verbose_name='URL Link to results'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='result',
|
||||||
|
unique_together={('crew', 'order')},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
boatmovers/migrations/0013_alter_crew_athletes.py
Normal file
18
boatmovers/migrations/0013_alter_crew_athletes.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-25 13:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0012_auto_20220625_1328'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='crew',
|
||||||
|
name='athletes',
|
||||||
|
field=models.ManyToManyField(related_name='athlete_crews', to='boatmovers.Athlete'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
boatmovers/migrations/0014_athlete_dummy.py
Normal file
18
boatmovers/migrations/0014_athlete_dummy.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-29 15:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0013_alter_crew_athletes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='athlete',
|
||||||
|
name='dummy',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
boatmovers/migrations/0015_alter_athlete_unique_together.py
Normal file
17
boatmovers/migrations/0015_alter_athlete_unique_together.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-29 17:44
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0014_athlete_dummy'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='athlete',
|
||||||
|
unique_together={('first_name', 'last_name', 'birth_year', 'gender')},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
boatmovers/migrations/0016_race_gender.py
Normal file
18
boatmovers/migrations/0016_race_gender.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-06-29 18:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('boatmovers', '0015_alter_athlete_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='race',
|
||||||
|
name='gender',
|
||||||
|
field=models.CharField(choices=[('m', 'M'), ('f', 'F')], default='m', max_length=200),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
boatmovers/migrations/__init__.py
Normal file
0
boatmovers/migrations/__init__.py
Normal file
256
boatmovers/models.py
Normal file
256
boatmovers/models.py
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.utils import timezone
|
||||||
|
import trueskill
|
||||||
|
|
||||||
|
from rowers.utils import dologging
|
||||||
|
|
||||||
|
def current_day(ttz=None):
|
||||||
|
if ttz is None:
|
||||||
|
return (datetime.datetime.now(tz=timezone.utc)).date()
|
||||||
|
return datetime.datetime.utcnow().astimezone(pytz.timezone(ttz)).date()
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
class Athlete(models.Model):
|
||||||
|
first_name = models.CharField(max_length=200)
|
||||||
|
last_name = models.CharField(max_length=200)
|
||||||
|
club = models.CharField(max_length=200)
|
||||||
|
trueskill_mu = models.FloatField(default=25.)
|
||||||
|
trueskill_sigma = models.FloatField(default=25./3.)
|
||||||
|
trueskill_exposed = models.FloatField(default=0)
|
||||||
|
birth_year = models.IntegerField(default=1972)
|
||||||
|
gender = models.CharField(max_length=200, choices=(('m','M'),('f','F')))
|
||||||
|
dummy = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('first_name','last_name','birth_year','gender')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u'{f} {l}'.format(f = self.first_name, l=self.last_name)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
name = '{f} {l}'.format(f = self.first_name, l = self.last_name)
|
||||||
|
athletes = Athlete.objects.filter(gender=self.gender)
|
||||||
|
if self.pk is not None:
|
||||||
|
athletes = athletes.exclude(pk=self.pk)
|
||||||
|
for a in athletes:
|
||||||
|
aname = '{f} {l}'.format(f = a.first_name, l = a.last_name)
|
||||||
|
if name == aname:
|
||||||
|
raise ValidationError("Duplicate:{id}".format(id=a.id))
|
||||||
|
rating = trueskill.Rating(self.trueskill_mu, self.trueskill_sigma)
|
||||||
|
self.trueskill_exposed = trueskill.expose(rating)
|
||||||
|
|
||||||
|
super(Athlete, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return "/boatmovers/athlete/%i/" % self.id
|
||||||
|
|
||||||
|
class athleteForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Athlete
|
||||||
|
fields = ['first_name','last_name','club','birth_year']
|
||||||
|
|
||||||
|
class Crew(models.Model):
|
||||||
|
athletes = models.ManyToManyField(Athlete, related_name='athlete_crews')
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u'{n}'.format(n=self.name)
|
||||||
|
|
||||||
|
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
|
||||||
|
fields = ['name', 'athletes']
|
||||||
|
|
||||||
|
class Race(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
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=False)
|
||||||
|
gender = models.CharField(max_length=200,choices=(('m','M'),('f','F')),default='m')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('date','name')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
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)):
|
||||||
|
raise ValidationError(
|
||||||
|
"Cannot have the same crew more than one time in a race"
|
||||||
|
)
|
||||||
|
|
||||||
|
#if len(athletes) != len(set(athletes)):
|
||||||
|
# raise ValidationError(
|
||||||
|
# "Cannot have the same athlete in different crews in a race"
|
||||||
|
# )
|
||||||
|
|
||||||
|
super(Race, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def validate(self, verbose=False):
|
||||||
|
if len(self.results.all()) < 2:
|
||||||
|
if verbose:
|
||||||
|
print('False: Less than 2 results')
|
||||||
|
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:
|
||||||
|
if verbose:
|
||||||
|
print('False: crew {c} has different crew size'.format(c=result.crew))
|
||||||
|
self.verified = False
|
||||||
|
self.save()
|
||||||
|
return False
|
||||||
|
|
||||||
|
if l not in [1,2,4,8]:
|
||||||
|
if verbose:
|
||||||
|
print('False: Crew size not in 1, 2, 4, or 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():
|
||||||
|
if not athlete.dummy:
|
||||||
|
athletes.append(athlete.id)
|
||||||
|
|
||||||
|
if len(crews) != len(set(crews)):
|
||||||
|
if verbose:
|
||||||
|
print('False: Same crew competing twice')
|
||||||
|
self.verified = False
|
||||||
|
self.save()
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(athletes) != len(set(athletes)):
|
||||||
|
if verbose:
|
||||||
|
print('False: Duplicate 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()
|
||||||
|
u = '{id},{f},{l},{mu},{sigma},{rid},{rname}'.format(
|
||||||
|
id = id,
|
||||||
|
f = athlete.first_name,
|
||||||
|
l = athlete.last_name,
|
||||||
|
mu = rating.mu,
|
||||||
|
sigma = rating.sigma,
|
||||||
|
rid = self.id,
|
||||||
|
rname = self.name,
|
||||||
|
)
|
||||||
|
dologging('ratings.csv',u)
|
||||||
|
|
||||||
|
self.processed = True
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class raceForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Race
|
||||||
|
fields = ['name','date','resulturl','crew_size','gender']
|
||||||
|
|
||||||
|
|
||||||
|
class Result(models.Model):
|
||||||
|
crew = models.ForeignKey(Crew, on_delete=models.CASCADE,
|
||||||
|
related_name='results')
|
||||||
|
race = models.ForeignKey(Race, on_delete=models.CASCADE,
|
||||||
|
related_name='results')
|
||||||
|
order = models.PositiveIntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
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()
|
||||||
|
athletes = []
|
||||||
|
for result in allresults:
|
||||||
|
for athlete in result.crew.athletes.all():
|
||||||
|
athletes.append(athlete.id)
|
||||||
|
if result.crew.id == self.crew.id:
|
||||||
|
raise ValidationError(
|
||||||
|
"Cannot have the same crew more than one time in a race"
|
||||||
|
)
|
||||||
|
if len(athletes) != len(set(athletes)):
|
||||||
|
print([item for item, count in collections.Counter(athletes).items() if count>1])
|
||||||
|
raise ValidationError(
|
||||||
|
"Cannot have the same athlete in different crews in a race"
|
||||||
|
)
|
||||||
|
|
||||||
|
super(Result,self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class resultForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Result
|
||||||
|
fields = ['crew','race','order']
|
||||||
111
boatmovers/results.py
Normal file
111
boatmovers/results.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import trueskill
|
||||||
|
from trueskill import Rating, rate
|
||||||
|
|
||||||
|
class Athlete:
|
||||||
|
def __init__(self, first_name, last_name, club, birth_year, mu=25, sigma=25./3.):
|
||||||
|
self.first_name = first_name
|
||||||
|
self.last_name = last_name
|
||||||
|
self.club = club
|
||||||
|
self.birth_year = birth_year
|
||||||
|
self.rating = Rating(mu, sigma)
|
||||||
|
|
||||||
|
def expose(self):
|
||||||
|
return trueskill.expose(self.rating)
|
||||||
|
|
||||||
|
def setrating(self, rating):
|
||||||
|
self.rating = rating
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u'{f} {l} {c} - {s:.2f}'.format(
|
||||||
|
f=self.first_name,
|
||||||
|
l=self.last_name,
|
||||||
|
c=self.club,
|
||||||
|
s=self.expose()
|
||||||
|
)
|
||||||
|
|
||||||
|
class Crew:
|
||||||
|
def __init__(self, athletes, name):
|
||||||
|
self.athletes = athletes
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
return len(self.athletes)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u'{n}'.format(n=self.name)
|
||||||
|
|
||||||
|
class Result:
|
||||||
|
def __init__(self, crews, name, validated=False, processed=False):
|
||||||
|
self.crews = crews
|
||||||
|
self.name = name
|
||||||
|
self.verified = validated
|
||||||
|
self.processed = processed
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
# crews need to be more than 2
|
||||||
|
if len(self.crews) < 2:
|
||||||
|
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.verified = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
# crew length need to be 1, 2, 4 or 8
|
||||||
|
if l not in [1,2,4,8]:
|
||||||
|
self.verified = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
# cannot have same crew multiple times in same race
|
||||||
|
if len(self.crews) != len(set(self.crews)):
|
||||||
|
self.verified = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
# cannot have same athletes in different crews in same race
|
||||||
|
allathletes = []
|
||||||
|
for crew in self.crews:
|
||||||
|
for athlete in crew.athletes:
|
||||||
|
allathletes.append(athlete)
|
||||||
|
|
||||||
|
if len(allathletes) != len(set(allathletes)):
|
||||||
|
self.verified = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.verified = True
|
||||||
|
return self.verified
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
if not self.verified:
|
||||||
|
if not self.validate():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.processed:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# validate the race
|
||||||
|
ratings = list([athlete.rating for athlete in crew.athletes] for crew in self.crews)
|
||||||
|
result = rate(ratings, ranks = list(range(len(self.crews))))
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
|
||||||
|
for c in result:
|
||||||
|
for rating in c:
|
||||||
|
self.crews[i].athletes[j].setrating(rating)
|
||||||
|
j += 1
|
||||||
|
i += 1
|
||||||
|
j = 0
|
||||||
|
|
||||||
|
self.processed = True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
s = self.name + ': '
|
||||||
|
for crew in self.crews:
|
||||||
|
s = s + str(crew) + ', '
|
||||||
|
|
||||||
|
return s[:-2]
|
||||||
153
boatmovers/scrapers.py
Normal file
153
boatmovers/scrapers.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import re
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import requests
|
||||||
|
from boatmovers.models import *
|
||||||
|
import pandas as pd
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
url_heineken = ''
|
||||||
|
|
||||||
|
|
||||||
|
def csv_reader(filename,raceid,clubcol='Ploeg',bankjes=['Slag'],uitslag='Pos',gender='m',
|
||||||
|
startorder=1):
|
||||||
|
race = Race.objects.get(id=raceid)
|
||||||
|
nr = race.crew_size
|
||||||
|
|
||||||
|
df = pd.read_csv(filename)
|
||||||
|
# replace column names if different
|
||||||
|
|
||||||
|
for row in df.itertuples():
|
||||||
|
order = row[df.columns.get_loc(uitslag)+1]
|
||||||
|
crewname = row[df.columns.get_loc(clubcol)+1]
|
||||||
|
crew = Crew(name=crewname)
|
||||||
|
crew.save()
|
||||||
|
|
||||||
|
for i in range(nr):
|
||||||
|
try:
|
||||||
|
naam = row[df.columns.get_loc(bankjes[i])+1].split(' ')
|
||||||
|
first_name = ' '.join(naam[:-1])
|
||||||
|
last_name = naam[-1]
|
||||||
|
dummy = False
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
first_name = str(row.Slag)
|
||||||
|
last_name = ''
|
||||||
|
dummy=False
|
||||||
|
except TypeError:
|
||||||
|
first_name = 'Unknown'
|
||||||
|
last_name = 'Athlete'
|
||||||
|
dummy=True
|
||||||
|
athletes = Athlete.objects.filter(first_name = first_name,
|
||||||
|
last_name = last_name,
|
||||||
|
gender=gender)
|
||||||
|
if len(athletes) >= 1:
|
||||||
|
athlete = athletes[0]
|
||||||
|
else:
|
||||||
|
athlete = Athlete(first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
club = crewname,
|
||||||
|
gender=gender,
|
||||||
|
dummy=dummy)
|
||||||
|
try:
|
||||||
|
athlete.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
text, id = e.message.split(':')
|
||||||
|
athlete = Athlete.objects.get(id=id)
|
||||||
|
|
||||||
|
print(athlete)
|
||||||
|
|
||||||
|
crew.athletes.add(athlete)
|
||||||
|
|
||||||
|
result = Result(
|
||||||
|
crew = crew,
|
||||||
|
race = race,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
if order>=startorder:
|
||||||
|
result.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
print(' ')
|
||||||
|
|
||||||
|
|
||||||
|
def time_team_scraper(url,raceid,gender='m',startorder=1):
|
||||||
|
race = Race.objects.get(id=raceid)
|
||||||
|
nr = race.crew_size
|
||||||
|
r = requests.get(url)
|
||||||
|
soup = BeautifulSoup(r.content,features='lxml')
|
||||||
|
tbl = soup.find('table')
|
||||||
|
order = 1
|
||||||
|
|
||||||
|
str = re.search('(.*)results(.*)',url)
|
||||||
|
base = str.groups()[0]
|
||||||
|
|
||||||
|
for tr in tbl.findAll("tr"):
|
||||||
|
trs = tr.findAll("td")
|
||||||
|
for each in trs:
|
||||||
|
try:
|
||||||
|
link = each.find('a')['href']
|
||||||
|
name = each.find('a').contents[0]
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
link = ''
|
||||||
|
name =''
|
||||||
|
|
||||||
|
if 'entry' in link:
|
||||||
|
print(order, name)
|
||||||
|
|
||||||
|
dfs = pd.read_html(base+link[3:])
|
||||||
|
df = dfs[0]
|
||||||
|
namen = df['naam']
|
||||||
|
|
||||||
|
crew = Crew(name=name)
|
||||||
|
crew.save()
|
||||||
|
for i in range(nr):
|
||||||
|
try:
|
||||||
|
names = df['naam'][i].split(' ')
|
||||||
|
first_name = ' '.join(names[:-1])
|
||||||
|
last_name = names[-1]
|
||||||
|
dummy=False
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
first_name = str(df['naam'][i])
|
||||||
|
last_name = ''
|
||||||
|
dummy=False
|
||||||
|
except TypeError:
|
||||||
|
first_name = 'Unknown'
|
||||||
|
last_name = 'Athlete'
|
||||||
|
dummy=True
|
||||||
|
athletes = Athlete.objects.filter(first_name = first_name,
|
||||||
|
last_name = last_name)
|
||||||
|
if len(athletes) >= 1:
|
||||||
|
athlete = athletes[0]
|
||||||
|
else:
|
||||||
|
athlete = Athlete(first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
club = name,
|
||||||
|
gender=gender,
|
||||||
|
dummy=dummy)
|
||||||
|
try:
|
||||||
|
athlete.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
text, id = e.message.split(':')
|
||||||
|
athlete = Athlete.objects.get(id=id)
|
||||||
|
|
||||||
|
print(athlete)
|
||||||
|
|
||||||
|
crew.athletes.add(athlete)
|
||||||
|
|
||||||
|
result = Result(
|
||||||
|
crew = crew,
|
||||||
|
race = race,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
if order >= startorder:
|
||||||
|
try:
|
||||||
|
result.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
order += 1
|
||||||
|
|
||||||
|
print('')
|
||||||
23
boatmovers/tasks.py
Normal file
23
boatmovers/tasks.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||||
|
from YamJam import yamjam
|
||||||
|
CFG = yamjam()['rowsandallapp']
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE",CFG['settings_name'])
|
||||||
|
except KeyError:
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE","rowsandall_app.settings")
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
||||||
|
|
||||||
|
from boatmovers.models import Race
|
||||||
|
from rq import get_current_job
|
||||||
|
from django_rq import job
|
||||||
|
|
||||||
|
@job
|
||||||
|
def race_process(id):
|
||||||
|
job = get_current_job()
|
||||||
|
|
||||||
|
race = Race.objects.get(id=id)
|
||||||
|
return race.process()
|
||||||
16
boatmovers/templates/athlete.html
Normal file
16
boatmovers/templates/athlete.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>
|
||||||
|
{{ athlete.first_name }} {{ athlete.last_name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
{% for result in results %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.order }}</td><td>{{ result.race.name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
124
boatmovers/templates/boatmovers.html
Normal file
124
boatmovers/templates/boatmovers.html
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<style>
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 50%;
|
||||||
|
border-width: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Boat Movers Ranglijst
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<a href="/boatmovers/?filter=f">Dames</a>
|
||||||
|
<a href="/boatmovers/?filter=m">Heren</a>
|
||||||
|
<a href="/boatmovers/">Alles</a>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="column">
|
||||||
|
<p>
|
||||||
|
<table width="100%" class="listtable shortpadded">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Rank</th>
|
||||||
|
<th>Score</th>
|
||||||
|
<th>Name</th><th> </th>
|
||||||
|
<th>Club</th>
|
||||||
|
<th>Gender</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for athlete in athletes %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ forloop.counter }}</td>
|
||||||
|
<td>{{ athlete.trueskill_exposed|floatformat:2 }}</td>
|
||||||
|
<td>{{ athlete.first_name }}</td>
|
||||||
|
<td>{{ athlete.last_name }}</td>
|
||||||
|
<td>{{ athlete.club }}</td>
|
||||||
|
<td>{{ athlete.gender }}</td>
|
||||||
|
<td><a href="/boatmovers/athlete/{{ athlete.id }}/">results</a></td>
|
||||||
|
{% if user.is_authenticated and user.is_staff %}
|
||||||
|
<td><a href="/admin/boatmovers/athlete/{{ athlete.id }}/change/">edit</a></td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<p>
|
||||||
|
Deze ranglijst is een experimentele lijst gebaseerd op wedstrijduitslagen
|
||||||
|
die op het internet te vinden zijn. De focus is op Nederlandse Masters-wedstrijden
|
||||||
|
volgens KNRB-regels.
|
||||||
|
De inspiratie voor deze site kwam van <a href="https://rowingstats.com/">Rowing Stats</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Zie onze <a href="faq">F.A.Q.</a> voor een beschrijving van hoe wij de ranglijst
|
||||||
|
berekenen.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Als je je naam hier wilt laten verwijderen, kan dat natuurlijk. Stuur ons
|
||||||
|
een emailtje op info@rowsandall.com.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Als je een uitslag mist, kun je ons dat laten weten door de "Nieuwe Race" link onder.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
De ranglijst bevat nu resultaten van de volgende races:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
{% for race in races %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ race.date }}</td><td>{{ race.name }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="race/{{ race.id }}">View Race</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
{% if user.is_authenticated and user.is_staff %}
|
||||||
|
{% if new_races %}
|
||||||
|
<p>
|
||||||
|
Niet verwerkte races:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
{% for race in new_races %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ race.date }}</td><td>{{ race.name }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="race/{{ race.id }}">Manage Race</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<a href="athlete/add/">Nieuwe Roeier</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="crew/add/">Nieuwe Ploeg</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<a href="race/add/">Nieuwe Race</a>
|
||||||
|
</p>
|
||||||
|
{% if user.is_authenticated and user.is_staff %}
|
||||||
|
<p>
|
||||||
|
<a href="result/add/">Nieuwe uitslag</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
7
boatmovers/templates/boatmovers/athlete_form.html
Normal file
7
boatmovers/templates/boatmovers/athlete_form.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
4
boatmovers/templates/boatmovers/crew_form.html
Normal file
4
boatmovers/templates/boatmovers/crew_form.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
8
boatmovers/templates/boatmovers/race_form.html
Normal file
8
boatmovers/templates/boatmovers/race_form.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<p>Create new race</p>
|
||||||
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
7
boatmovers/templates/boatmovers/result_form.html
Normal file
7
boatmovers/templates/boatmovers/result_form.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
41
boatmovers/templates/boatmovers_base.html
Normal file
41
boatmovers/templates/boatmovers_base.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% load leaflet_tags %}
|
||||||
|
{% load cookielaw_tags %}
|
||||||
|
|
||||||
|
{% block filters %}
|
||||||
|
{% endblock %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}Rowsandall Boatmovers{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/resetnew.css" />
|
||||||
|
<link rel="stylesheet" href="/static/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.12.0/css/all.css" integrity="sha384-ekOryaXPbeCpWQNxMwSWVvQ0+1VrStoPJq54shlYhR8HzQgig1v5fas6YgOqLoKz" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="/static/css/styles2.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/text2.css" />
|
||||||
|
<link rel="stylesheet" href="/static/css/rowsandall2.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<a href="/boatmovers/">Ranglijst</a>
|
||||||
|
<a href="/boatmovers/faq/">F.A.Q.</a>
|
||||||
|
</div>
|
||||||
|
{% if WARNING_MESSAGE != '' %}
|
||||||
|
{{ WARNING_MESSAGE }}
|
||||||
|
{% endif %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>
|
||||||
|
{{ message|safe }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
{% block scripts %} {% endblock %}
|
||||||
22
boatmovers/templates/crew.html
Normal file
22
boatmovers/templates/crew.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<h1>
|
||||||
|
{{ crew.name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Athlete</th>
|
||||||
|
<th> </th>
|
||||||
|
<th>Score</th>
|
||||||
|
</tr>
|
||||||
|
{% for athlete in athletes %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ athlete.first_name }}</td>
|
||||||
|
<td>{{ athlete.last_name }}</td>
|
||||||
|
<td>{{ athlete.trueskill_exposed|floatformat:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
14
boatmovers/templates/csvform.html
Normal file
14
boatmovers/templates/csvform.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>
|
||||||
|
{{ race.name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<form method="post" enctype="multipart/form-data">{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="hidden" name="action" value="create" />
|
||||||
|
<input type="submit" value="Submit"/>
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
137
boatmovers/templates/faq.html
Normal file
137
boatmovers/templates/faq.html
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Hoe werkt de ranking?</h1>
|
||||||
|
|
||||||
|
<p>De ranking is gebaseerd op het “TrueSkill ranking system”. Voor iedere roeier worden twee variabelen
|
||||||
|
berekend. De eerste variabele (“mu”) geeft aan hoe “goed” je gemiddeld bent (in goede uitslagen van
|
||||||
|
wedstrijden). De tweede variabele (“sigma”) geeft de mate van onzekerheid in de schatting van “mu”.
|
||||||
|
De score op de site is berekend als “mu – 3 sigma”. Vooral als je net begint, is dit een conservatieve
|
||||||
|
schatting.
|
||||||
|
</p>
|
||||||
|
<p>Iedere keer als er een wedstrijduitslag wordt verwerkt waar jij aan hebt deelgenomen, wordt je score
|
||||||
|
opnieuw berekend. Er wordt gekeken naar de (gemiddelde) scores van alle ploegen die aan de wedstrijd
|
||||||
|
hebben deelgenomen. Dan wordt er gekeken hoe waarschijnlijk deze uitslag is, gebaseerd op de scores
|
||||||
|
van de ploegen.</p>
|
||||||
|
<p>
|
||||||
|
Als je beter eindigt dan een ploeg met een hogere score voor de wedstrijd, zal jouw score stijgen.
|
||||||
|
Andersom, als je lager eindigt dan een ploeg met een lagere score, zal jouw score dalen. Hoeveel je
|
||||||
|
score stijgt of daalt, hangt af van hoe “onwaarschijnlijk” de uitslag is. Hoe onwaarschijnlijker je winst,
|
||||||
|
hoe meer je stijgt.</p>
|
||||||
|
<p>
|
||||||
|
Ook de onzekerheid in je “mu” zal dalen door aan wedstrijden deel te nemen. Als je begint, zal het dus
|
||||||
|
relatief makkelijk zijn om te stijgen in de ranking.</p>
|
||||||
|
<p>
|
||||||
|
<h1>Hoe kan ik stijgen op de ranking?</h1>
|
||||||
|
<p>Hoe vaker je aan wedstrijden deelneemt, hoe beter we weten hoe goed je bent. Zeker in het begin kun
|
||||||
|
je dus stijgen door veel wedstrijden te roeien. Je zult sneller stijgen door skiffwedstrijden te winnen dan
|
||||||
|
met wedstrijden in de 8. Dit komt doordat een skiff-uitslag de mate van onzekerheid in je skills sneller
|
||||||
|
doet dalen. Dit betekent niet dat je als 8-roeier niet bovenaan de ranking kunt staan. Je moet alleen
|
||||||
|
meer wedstrijden roeien voordat je “boven komt drijven”.</p>
|
||||||
|
<p>
|
||||||
|
Ook zul je sneller stijgen (en dalen!) door deel te nemen aan wedstrijden met een groot startveld.
|
||||||
|
Zodra je een paar wedstrijden hebt geroeid en het systeem een goed idee heeft van je niveau, is het
|
||||||
|
zaak om beter te worden en ploegen te verslaan die boven je staan. Het is dus verstandig om deel te
|
||||||
|
nemen aan goedbezochte wedstrijden.</p>
|
||||||
|
<h1>
|
||||||
|
Als het makkelijker is om te stijgen als je net begint, zijn dan ook mijn eerste races het belangrijkst
|
||||||
|
voor de plaats in de ranglijst?</h1>
|
||||||
|
<p>
|
||||||
|
Nee, dat is niet zo. TrueSkill legt meer nadruk op recente uitslagen dan aan oudere uitslagen. Als je dus
|
||||||
|
vaak tegen dezelfde ploegen start, zullen de meest recente winnaars hoger staan dan de oudere
|
||||||
|
winnaars.</p>
|
||||||
|
<h1>
|
||||||
|
Is dit een “ranglijst aller tijden” of een seizoensranking?</h1>
|
||||||
|
<p>Dit is een ranglijst aller tijden. Omdat veel roeiers in viertjes en achten starten, is een seizoen eigenlijk te
|
||||||
|
kort om skiffeurs te kunnen voorbijgaan op de ranking. Daarom laat ik de ranglijst oneindig doorlopen.
|
||||||
|
</p>
|
||||||
|
<h1>Als ik hoog sta in de ranking, kan ik dan hoog blijven door niet meer aan wedstrijden deel te nemen?</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Dat is inderdaad mogelijk. We gaan ervan uit dat je het leuker vindt om wedstrijden te roeien dan om
|
||||||
|
hoog in de ranking te staan. Ook hopen we dat de ranking mensen aanspoort om elkaar uit te dagen aan
|
||||||
|
wedstrijden mee te doen.</p>
|
||||||
|
<p>
|
||||||
|
Mocht blijken dat dit echt een probleem is, dan is het eenvoudig om een filter op laatste deelname aan
|
||||||
|
een wedstrijd toe te voegen. Persoonlijk vind ik het ook wel mooi als “legendarische” roeiers lang na
|
||||||
|
hun carriere hoog in de ranking blijven staan, ook al is er geen gelegenheid meer om tegen ze te starten.</p>
|
||||||
|
|
||||||
|
<h1>Ik roei altijd met dezelfde ploeg. Hoe kan het systeem een onderscheid in niveau bepalen tussen mijn
|
||||||
|
ploegleden onderling?</h1>
|
||||||
|
|
||||||
|
<p>Als alle leden van jouw ploeg altijd alleen in deze ene ploeg starten, zal je altijd dezelfde score houden.
|
||||||
|
Maar als een van jouw ploegleden ook wedstrijden start in een andere ploeg, dan zal jullie score gaan
|
||||||
|
verschillen.</p>
|
||||||
|
|
||||||
|
<h1>Is het eerlijk om voor ploegen het gemiddelde niveau te gebruiken?</h1>
|
||||||
|
|
||||||
|
<p>Onze aanname is dat dat de eenvoudigste manier is die waarschijnlijk vrij eerlijk is. Als roeien alleen
|
||||||
|
afhankelijk is van de power (ergometer score) die je in de benen hebt, klopt deze aanname 100 procent.
|
||||||
|
Maar bij het roeien is ook techniek belangrijk, dus er zal (gelukkig) altijd een verrassingsaspect zijn.
|
||||||
|
Waarom ontbreekt mijn ploeg in de uitslag?</p>
|
||||||
|
|
||||||
|
<p>In sommige uitslagen staan ploegen met roeiers met dezelfde naam. Dit is waarschijnlijk een foutje bij
|
||||||
|
de inschrijving, of het kan zijn dat je toevallig tegen een naamgenoot geroeid hebt.
|
||||||
|
Het systeem identificeert roeiers door hun voornaam/achternaam en geslacht. We hebben op dit
|
||||||
|
moment geen goed systeem om onderscheid te maken tussen twee verschillende roeiers die toevallig
|
||||||
|
allebei “Jan Visser” heten. Je zou de clubnaam kunnen gebruiken, maar soms wisselen roeiers van club.
|
||||||
|
Ook staan in de uitslagen ploegen geïdentificeerd als “Willem III/RIC/Hoop” en is het onmogelijk om te
|
||||||
|
zien welk ploeglid bij Willem III roeit en welk ploeglid bij De Hoop.</p>
|
||||||
|
|
||||||
|
<h1>Welke wedstrijden worden meegenomen?</h1>
|
||||||
|
|
||||||
|
<p>Op dit moment verwerken wij uitslagen van de volgende wedstrijden:
|
||||||
|
<ul>
|
||||||
|
<li>Heineken Roeivierkamp (einduitslag over alle afstanden)</li>
|
||||||
|
<li>Head of the River Amstel</li>
|
||||||
|
<li>Tweehead</li>
|
||||||
|
<li>Skiffhead</li>
|
||||||
|
<li>Spaarne Lenterace</li>
|
||||||
|
<li>Dutch Masters Open (alleen uitslagen van races die zonder startverschil worden geroeid)</li>
|
||||||
|
<li>Cottwich</li>
|
||||||
|
<li>Eemhead</li>
|
||||||
|
<li>Tromp Boat Races</li>
|
||||||
|
<li>Novembervieren</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>Van deze wedstrijden nemen we uitslagen van veteranenvelden. We kijken niet naar het clubveld, of
|
||||||
|
open velden.</p>
|
||||||
|
|
||||||
|
<p>De reden dat we deze wedstrijden kiezen is dat het goedbezochte wedstrijden zijn met een online
|
||||||
|
uitslag die makkelijk te verwerken is (Time-Team, hoesnelwasik.nl). Het is lastig om wedstrijden te
|
||||||
|
verwerken waar niet de namen van alle ploegleden zijn te achterhalen. Vaak wordt er een PDF-je
|
||||||
|
gepubliceerd met alleen de naam van de slagroeier.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Dit betekent niet dat we niet open staan voor het toevoegen van nieuwe wedstrijden. Volg de “Add
|
||||||
|
Race” link en vul de gegevens in. Wij krijgen dan een automatische email en gaan ernaar kijken.
|
||||||
|
Kan ik ook een onderling duel aangaan met iemand en dan de uitslag mee laten tellen?</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Het is belangrijk dat de uitslag van de wedstrijd onafhankelijk te controleren is. Het makkelijkst is dus af
|
||||||
|
te spreken allebei aan een van de meetellende wedstrijden te starten, of ons te vragen een (onder
|
||||||
|
KNRB-auspiciën geroeide) wedstrijd toe te voegen,
|
||||||
|
waarvan de uitslag online staat met alle ploegleden.</p>
|
||||||
|
|
||||||
|
<h1>Ik wil niet op deze ranking staan. Kun je mij verwijderen?</h1>
|
||||||
|
|
||||||
|
<p>Ja, dat kan op twee manieren:
|
||||||
|
<ol>
|
||||||
|
<li>Ik kan zorgen dat je naam niet in de ranking vermeld wordt. Je scores worden dan wel gebruikt
|
||||||
|
als je in ploegen start met andere mensen op de ranglijst. Je naam is dan nog wel zichtbaar als
|
||||||
|
mensen op die ploegen klikken in de uitslagenlijst. Aangezien deze uitslagen identiek zijn aan
|
||||||
|
wat al op het internet te vinden is, neem ik aan dat dit geen probleem is.
|
||||||
|
</li>
|
||||||
|
<li>Ik kan zorgen dat je uit de ranking verwijderd wordt en blijft. Bij het verwerken van uitslagen zal
|
||||||
|
ik dan jouw hele ploeg moeten verwijderen. De consequentie is dat je ploegmaten alleen
|
||||||
|
kunnen stijgen en dalen door wedstrijden zonder jou te roeien.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>De enige informatie in het systeem is je voornaam, achternaam, geslacht en in welke ploegen je bent
|
||||||
|
gestart. Dit is informatie die in wedstrijduitslagen al beschikbaar is. Door in te schrijven voor deze
|
||||||
|
wedstrijden heb je al ingestemd met het openbaar maken van jouw naam en ploeggegevens in de
|
||||||
|
uitslag. Daarom vind ik het een redelijke aanname dat je geen bezwaar hebt tegen het opgenomen zijn
|
||||||
|
in deze ranking, maar als je het vraagt haal ik je er met alle plezier vanaf.</p>
|
||||||
|
{% endblock %}
|
||||||
63
boatmovers/templates/race.html
Normal file
63
boatmovers/templates/race.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{% extends "boatmovers_base.html" %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>
|
||||||
|
{{ race.name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
{{ race.date }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a target="_" href="{{ race.resulturl }}">Results</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Order</th>
|
||||||
|
<th>Crew</th><td></td>
|
||||||
|
</tr>
|
||||||
|
{% for result in results %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ result.order }}</td>
|
||||||
|
<td><a href="/boatmovers/crew/{{ result.crew.id }}/">{{ result.crew.name }}</a></td>
|
||||||
|
{% if result.crew.id in duplicate_crews %}
|
||||||
|
<td>!</td>
|
||||||
|
{% elif result.crew.id in duplicate_athletes_crews %}
|
||||||
|
<td>!</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
{% if user.is_authenticated and user.is_staff %}
|
||||||
|
{% if race.verified %}
|
||||||
|
<p>
|
||||||
|
Race has been verified
|
||||||
|
</p>
|
||||||
|
{% if race.processed %}
|
||||||
|
<p>
|
||||||
|
Race has been processed
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
Race is not processed. <a href="process/">Process Race</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
Race is not verified. <a href="verify/">Verify Race</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="/boatmovers/race/{{ race.id }}/deleteresults/">Remove all results</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if not race.verified and not race.processed %}
|
||||||
|
<p>
|
||||||
|
<a href="/boatmovers/result/add/">Add Result</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="/boatmovers/race/{{ race.id }}/csv/">Add Result CSV</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
3
boatmovers/tests.py
Normal file
3
boatmovers/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
25
boatmovers/urls.py
Normal file
25
boatmovers/urls.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
|
import boatmovers.views as views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'athlete/add/$',views.AthleteCreateView.as_view(),name='athlete_add'),
|
||||||
|
url(r'athlete/(?P<id>\d+)/$',views.athlete_view,name='athlete_view'),
|
||||||
|
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<id>\d+)/$',views.race_view,name='race_view'),
|
||||||
|
url(r'race/(?P<id>\d+)/csv/$',views.race_add_csv,name='race_add_csv'),
|
||||||
|
url(r'race/(?P<id>\d+)/verify/$',views.race_verify,name='race_verify'),
|
||||||
|
url(r'race/(?P<id>\d+)/process/$',views.race_process,name='race_process'),
|
||||||
|
url(r'race/(?P<id>\d+)/deleteresults/$',views.race_delete_results,
|
||||||
|
name='race_delete_results'),
|
||||||
|
url(r'crew/(?P<id>\d+)/$',views.crew_view,name='crew_view'),
|
||||||
|
url(r'^$',views.boatmovers_view,name='boatmovers'),
|
||||||
|
path(r'faq/', TemplateView.as_view(template_name='faq.html'), name='faq'),
|
||||||
|
#url(r'(?P<filter>\b[0-9A-Fa-f]+\b)/$',views.boatmovers_view,name='boatmovers')
|
||||||
|
]
|
||||||
215
boatmovers/views.py
Normal file
215
boatmovers/views.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
from django.views.generic.edit import CreateView
|
||||||
|
from boatmovers.models import Athlete, Crew, Race, Result
|
||||||
|
import boatmovers.tasks as tasks
|
||||||
|
from boatmovers.forms import CsvForm
|
||||||
|
from boatmovers.scrapers import *
|
||||||
|
#from rowers.rows import handle_uploaded_file
|
||||||
|
import django_rq
|
||||||
|
queue = django_rq.get_queue('high')
|
||||||
|
|
||||||
|
class AthleteCreateView(CreateView):
|
||||||
|
model = Athlete
|
||||||
|
fields = [
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'birth_year',
|
||||||
|
'gender',
|
||||||
|
'club',
|
||||||
|
]
|
||||||
|
success_url = '/boatmovers/'
|
||||||
|
|
||||||
|
class CrewCreateView(CreateView):
|
||||||
|
model = Crew
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'athletes'
|
||||||
|
]
|
||||||
|
|
||||||
|
success_url = '/boatmovers/'
|
||||||
|
|
||||||
|
class RaceCreateView(CreateView):
|
||||||
|
model = Race
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'resulturl',
|
||||||
|
'date',
|
||||||
|
'crew_size',
|
||||||
|
'gender',
|
||||||
|
#'resultlist',
|
||||||
|
]
|
||||||
|
|
||||||
|
success_url = '/boatmovers/'
|
||||||
|
|
||||||
|
class ResultCreateView(CreateView):
|
||||||
|
model = Result
|
||||||
|
fields = [
|
||||||
|
'crew',
|
||||||
|
'race',
|
||||||
|
'order'
|
||||||
|
]
|
||||||
|
|
||||||
|
success_url = '/boatmovers/'
|
||||||
|
|
||||||
|
def athlete_view(request,id=0):
|
||||||
|
athlete = get_object_or_404(Athlete, pk=id)
|
||||||
|
crews = athlete.athlete_crews
|
||||||
|
resultslist = []
|
||||||
|
|
||||||
|
for crew in crews.values():
|
||||||
|
c = Crew.objects.get(id=crew['id'])
|
||||||
|
results = Result.objects.filter(crew=c)
|
||||||
|
for result in results:
|
||||||
|
resultslist.append(result)
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'athlete.html',
|
||||||
|
{
|
||||||
|
'athlete':athlete,
|
||||||
|
'results':resultslist,
|
||||||
|
})
|
||||||
|
|
||||||
|
def boatmovers_view(request):
|
||||||
|
athletes = Athlete.objects.filter(trueskill_exposed__gt=0,
|
||||||
|
dummy=False).order_by('-trueskill_exposed','-birth_year','last_name','first_name')
|
||||||
|
|
||||||
|
filter = request.GET.get('filter','all')
|
||||||
|
|
||||||
|
if filter == 'm':
|
||||||
|
athletes = athletes.exclude(gender='f')
|
||||||
|
elif filter == 'f':
|
||||||
|
athletes = athletes.exclude(gender='m')
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
crews = []
|
||||||
|
athletes = []
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
crews.append(result.crew.id)
|
||||||
|
for athlete in result.crew.athletes.all():
|
||||||
|
athletes.append(athlete.id)
|
||||||
|
|
||||||
|
# duplicates
|
||||||
|
duplicate_athletes = [item for item, count in collections.Counter(athletes).items() if count>1]
|
||||||
|
duplicate_crews = [item for item, count in collections.Counter(crews).items() if count>1]
|
||||||
|
duplicate_athletes_crews = []
|
||||||
|
for athlete_id in duplicate_athletes:
|
||||||
|
athlete = Athlete.objects.get(id=athlete_id)
|
||||||
|
crews = [crew.id for crew in athlete.athlete_crews.all()]
|
||||||
|
for crew in crews:
|
||||||
|
duplicate_athletes_crews.append(crew)
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'race.html',
|
||||||
|
{
|
||||||
|
'race':race,
|
||||||
|
'results':results,
|
||||||
|
'duplicate_athletes':duplicate_athletes,
|
||||||
|
'duplicate_crews':duplicate_crews,
|
||||||
|
'duplicate_athletes_crews':duplicate_athletes_crews
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 handle_uploaded_file(f):
|
||||||
|
with open('media/results.csv', 'wb+') as destination:
|
||||||
|
for chunk in f.chunks():
|
||||||
|
destination.write(chunk)
|
||||||
|
|
||||||
|
def race_add_csv(request, id=0):
|
||||||
|
race = get_object_or_404(Race, pk=id)
|
||||||
|
if race.verified or race.processed:
|
||||||
|
messages.error(request,"Cannot upload CSV file for processed or verified race")
|
||||||
|
url = reverse("race_view",kwargs={'id':id})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
form = CsvForm()
|
||||||
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
form = CsvForm(request.POST, request.FILES)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
handle_uploaded_file(request.FILES['file'])
|
||||||
|
if race.crew_size == 1:
|
||||||
|
bankjes = ['Slag']
|
||||||
|
elif race.crew_size == 2:
|
||||||
|
bankjes = ['Slag','Boeg']
|
||||||
|
elif race.crew_size == 4:
|
||||||
|
bankjes = ['Slag','2','3','Boeg']
|
||||||
|
elif race.crew_size == 8:
|
||||||
|
bankjes = ['Slag','2','3','4','5','6','7','Boeg']
|
||||||
|
|
||||||
|
csv_reader('media/results.csv',race.id,bankjes=bankjes,gender=race.gender)
|
||||||
|
|
||||||
|
url = reverse('race_view',kwargs={'id':race.id})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'csvform.html',
|
||||||
|
{
|
||||||
|
'race':race,
|
||||||
|
'form':form,
|
||||||
|
})
|
||||||
|
|
||||||
|
def race_process(request, id=0):
|
||||||
|
race = get_object_or_404(Race, pk=id)
|
||||||
|
job = queue.enqueue(tasks.race_process,race.id)
|
||||||
|
#outcome = race.process()
|
||||||
|
messages.info(request,"Your race is being processed. Reload to get the new status.")
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverse('race_view',kwargs={'id':race.id}))
|
||||||
|
|
||||||
|
def race_delete_results(request, id=0):
|
||||||
|
race = get_object_or_404(Race, pk=id)
|
||||||
|
results = race.results.all()
|
||||||
|
|
||||||
|
if not race.processed:
|
||||||
|
for result in results:
|
||||||
|
result.delete()
|
||||||
|
messages.info(request,'Results have been removed')
|
||||||
|
else:
|
||||||
|
messages.error(request,'Cannot remove processed results')
|
||||||
|
|
||||||
|
url = reverse('race_view',kwargs={'id':race.id})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
def crew_view(request, id=0):
|
||||||
|
crew = get_object_or_404(Crew, pk=id)
|
||||||
|
athletes = crew.athletes.all().order_by("-trueskill_exposed")
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'crew.html',
|
||||||
|
{
|
||||||
|
'crew':crew,
|
||||||
|
'athletes':athletes
|
||||||
|
})
|
||||||
2036
cvkbrno/views.py
2036
cvkbrno/views.py
File diff suppressed because it is too large
Load Diff
1089
ratings.csv
Normal file
1089
ratings.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -76,6 +76,7 @@ INSTALLED_APPS = [
|
|||||||
'django_countries',
|
'django_countries',
|
||||||
'rules',
|
'rules',
|
||||||
'taggit',
|
'taggit',
|
||||||
|
'boatmovers',
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
@@ -137,10 +138,7 @@ TEMPLATES = [
|
|||||||
],
|
],
|
||||||
'libraries': {
|
'libraries': {
|
||||||
'staticfiles': 'django.templatetags.static',
|
'staticfiles': 'django.templatetags.static',
|
||||||
}
|
},
|
||||||
# 'loaders': [
|
|
||||||
# 'django.template.loaders.app_directories.Loader',
|
|
||||||
# ],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ urlpatterns += [
|
|||||||
{'next_page': '/'},
|
{'next_page': '/'},
|
||||||
name='logout',),
|
name='logout',),
|
||||||
re_path(r'^rowers/', include('rowers.urls')),
|
re_path(r'^rowers/', include('rowers.urls')),
|
||||||
# re_path(r'^survey/',include('survey.urls')),
|
re_path(r'^boatmovers/',include('boatmovers.urls')),
|
||||||
|
#re_path(r'^survey/',include('survey.urls')),
|
||||||
# re_path(r'^cvkbrno/',include('cvkbrno.urls')),
|
# re_path(r'^cvkbrno/',include('cvkbrno.urls')),
|
||||||
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
|
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
|
||||||
re_path(r'^call\_back', rowersviews.rower_process_callback),
|
re_path(r'^call\_back', rowersviews.rower_process_callback),
|
||||||
|
|||||||
Reference in New Issue
Block a user