Add basic algorithm and celery stuff

This commit is contained in:
Maxime Bloch 2020-07-22 04:02:13 +02:00
parent 6240f53ef5
commit ba17e04183
10 changed files with 133 additions and 5 deletions

View file

@ -0,0 +1,7 @@
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

View file

@ -40,6 +40,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django_celery_beat'
] + OWN_APPS ] + OWN_APPS
MIDDLEWARE = [ MIDDLEWARE = [

6
Makefile Normal file
View file

@ -0,0 +1,6 @@
celery:
celery -A KeRS worker -l info
beat:
celery -A KeRS beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
server:
python manage.py runserver

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.8 on 2020-07-21 21:36 # Generated by Django 3.0.8 on 2020-07-22 00:49
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -26,7 +26,7 @@ class Migration(migrations.Migration):
name='EventRegistration', name='EventRegistration',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('shirt_size', models.CharField(choices=[('I', 'Interested'), ('A', 'Admitted'), ('D', 'Denied')], max_length=1)), ('state', models.CharField(choices=[('I', 'Interested'), ('A', 'Admitted'), ('D', 'Denied')], max_length=1)),
('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')), ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],

View file

@ -0,0 +1,23 @@
# Generated by Django 3.0.8 on 2020-07-22 01:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('events', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='eventregistration',
old_name='event_id',
new_name='event',
),
migrations.RenameField(
model_name='eventregistration',
old_name='user_id',
new_name='user',
),
]

View file

@ -14,6 +14,9 @@ class EventRegistration(models.Model):
('A', 'Admitted'), ('A', 'Admitted'),
('D', 'Denied'), ('D', 'Denied'),
) )
event_id = models.ForeignKey(Event, on_delete=models.CASCADE) event = models.ForeignKey(Event, on_delete=models.CASCADE)
user_id = models.ForeignKey(CustomUser, on_delete=models.CASCADE) user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
state = models.CharField(max_length=1, choices=REGISTRATION_STATE) state = models.CharField(max_length=1, choices=REGISTRATION_STATE)
def __str__(self):
return f'Reservation[{self.user.username}:{self.event.date}:{self.state}]'

71
events/tasks.py Normal file
View file

@ -0,0 +1,71 @@
from datetime import date, timedelta
from typing import List
from KeRS.celery import app
from events.models import Event, EventRegistration
from users.models import CustomUser
def calc_score(user: CustomUser):
registrations_last_month = EventRegistration.objects.all().filter(user_id=user.id,
event__date__gt=date.today() - timedelta(days=30),
event__date__lte=date.today(),
state__exact='A')
score = 0
for r in registrations_last_month:
days_ago = (date.today() - r.event.date.date()).days
score += 1 / (days_ago + 1)
return score
@app.task(bind=True)
def assign_reservations(self):
"""
Chech if there are any events the next day.
If so, calculate the current score for every interested user and assign the ones with the lowest scores.
:param self:
:return:
"""
print("Assigning reservations")
print("======================")
# Get all events of tomorrow
events = Event.objects.all().filter(date__date=date.today() + timedelta(days=1))
# Reservations
registrations: List[EventRegistration] = EventRegistration.objects.all().filter(
event_id__in=map(lambda event: event.id, events),
state__exact='I')
if len(registrations) == 0:
print("NO REGISTRATIONS?")
# Relevant users
users = set(map(lambda r: r.user, registrations))
scores = list(map(calc_score, users))
queue = sorted(list(zip(users, scores)), key=lambda tup: tup[1])
print(f"Scores: {scores}")
print(f"Queue: {queue}")
for event in events:
print(f"EVENT: {event.date} - {event.capacity}")
event_registrations = list(filter(lambda r: r.event == event, registrations))
event_users = set(map(lambda r: r.user, event_registrations))
event_queue = list(filter(lambda element: element[0] in event_users, queue))
for user in event_queue[0:event.capacity]:
print(f"Selected {user[0]}")
r = EventRegistration.objects.get(
event_id=event.id,
user_id=user[0].id
)
r.state = 'A'
r.save()
for user in event_queue[event.capacity:]:
print(f"Denied {user[0]}")
r = EventRegistration.objects.get(
event_id=event.id,
user_id=user[0].id
)
r.state = 'D'
r.save()

View file

@ -4,4 +4,5 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('test/', views.view_score_stuff, name='score_stuff'),
] ]

View file

@ -3,6 +3,15 @@ from django.shortcuts import render
# Create your views here. # Create your views here.
from django.http import HttpResponse from django.http import HttpResponse
from events.tasks import assign_reservations
def index(request): def index(request):
return HttpResponse("Hello, world. You're at the polls index.") return HttpResponse(
)
def view_score_stuff(request):
return HttpResponse(
assign_reservations()
)

View file

@ -1,6 +1,13 @@
amqp==2.6.0
asgiref==3.2.10 asgiref==3.2.10
billiard==3.6.3.0
celery==4.4.6
Django==3.0.8 Django==3.0.8
future==0.18.2
kombu==4.6.11
mysqlclient==2.0.1 mysqlclient==2.0.1
pytz==2020.1 pytz==2020.1
PyYAML==5.3.1 PyYAML==5.3.1
redis==3.5.3
sqlparse==0.3.1 sqlparse==0.3.1
vine==1.3.0